Безопасное преобразование std::string_view в int (например, stoi или atoi)

Существует ли безопасный стандартный способ преобразования std::string_view в int?


Поскольку C++11 std::string позволяет нам использовать stoi для преобразования в int:

  std::string str = "12345";
  int i1 = stoi(str);              // Works, have i1 = 12345
  int i2 = stoi(str.substr(1,2));  // Works, have i2 = 23

  try {
    int i3 = stoi(std::string("abc"));
  } 
  catch(const std::exception& e) {
    std::cout << e.what() << std::endl;  // Correctly throws 'invalid stoi argument'
  }

Но stoi не поддерживает std::string_view. В качестве альтернативы мы могли бы использовать atoi, но нужно быть очень осторожным, например:

  std::string_view sv = "12345";
  int i1 = atoi(sv.data());              // Works, have i1 = 12345
  int i2 = atoi(sv.substr(1,2).data());  // Works, but wrong, have i2 = 2345, not 23

Таким образом, atoi тоже не работает, так как он основан на нулевом терминаторе '\0' (и, например, sv.substr не может просто вставить/добавить его).

Теперь, начиная с С++ 17, есть также from_chars, но, похоже, он не выдает ошибки при вводе неверных данных:

  try {
    int i3;
    std::string_view sv = "abc";
    std::from_chars(sv.data(), sv.data() + sv.size(), i3);
  }
  catch (const std::exception& e) {
    std::cout << e.what() << std::endl;  // Does not get called
  }

person Phil-ZXX    schedule 17.06.2019    source источник
comment
Это потому, что std::from_chars ничего не генерирует. Вместо этого он возвращает код ошибки.   -  person Yksisarvinen    schedule 17.06.2019


Ответы (3)


Функция std::from_chars не генерирует исключение, а только возвращает значение введите from_chars_result, который представляет собой структуру с двумя полями:

struct from_chars_result {
    const char* ptr;
    std::errc ec;
};

Вы должны проверить значения ptr и ec, когда функция возвращает:

#include <iostream>
#include <string>
#include <charconv>

int main()
{
    int i3;
    std::string_view sv = "abc";
    auto result = std::from_chars(sv.data(), sv.data() + sv.size(), i3);
    if (result.ec == std::errc::invalid_argument) {
        std::cout << "Could not convert.";
    }
}
person Ron    schedule 17.06.2019

К сожалению, нет стандартного способа, который выдал бы вам исключение, кроме std::from_chars. имеет код возвращаемого значения, который вы можете использовать:

#include <charconv>
#include <stdexcept>

template <class T, class... Args>
void from_chars_throws(const char* first, const char* last, T &t, Args... args) {
    std::from_chars_result res = std::from_chars(first, last, t, args... );

    // These two exceptions reflect the behavior of std::stoi.
    if (res.ec == std::errc::invalid_argument) {
        throw std::invalid_argument{"invalid_argument"};
    }
    else if (res.ec == std::errc::result_out_of_range) {
        throw std::out_of_range{"out_of_range"};
    }
}

Очевидно, вы можете создать из этого svtoi, svtol, но преимущество "расширения" from_chars заключается в том, что вам нужна только одна шаблонная функция.

person Holt    schedule 17.06.2019

Основываясь на отличных ответах @Ron и @Holt, вот небольшая оболочка вокруг std::from_chars(), которая возвращает необязательный (std::nullopt, когда ввод не удается проанализировать).

#include <charconv>
#include <optional>
#include <string_view>

std::optional<int> to_int(const std::string_view & input)
{
    int out;
    const std::from_chars_result result = std::from_chars(input.data(), input.data() + input.size(), out);
    if(result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range)
    {
        return std::nullopt;
    }
    return out;
}
person s3cur3    schedule 11.01.2021
comment
Как насчет того, когда ec является другим кодом неисправности, помимо этих двух? - person M.M; 01.03.2021
comment
@M.M, насколько я могу судить из документов CppReference, эти два единственные коды ошибок, которые std::from_chars() возвращает. - person s3cur3; 01.03.2021