Phil-ZXX
Phil-ZXX

Reputation: 3275

Safely convert std::string_view to int (like stoi or atoi)

Is there a safe standard way to convert std::string_view to int?


Since C++11 std::string lets us use stoi to convert to 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'
  }

But stoi does not support std::string_view. So alternatively, we could use atoi, but one has to be very careful, e.g.:

  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

So atoi does not work either, since it is based off the null-terminator '\0' (and e.g. sv.substr cannot simply insert/add one).

Now, since C++17 there is also from_chars, but it does not seem to throw when providing poor inputs:

  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
  }

Upvotes: 53

Views: 36407

Answers (6)

0xF
0xF

Reputation: 3718

As @some-guy mentioned in the comments, you need to examine both std::from_chars_result::ptr and std::from_chars_result::ec. Otherwise the parsing can happily stop in the middle of the string.

Here's a function template that accepts all numeric types:

#include <charconv>
#include <stdexcept>
#include <string_view>

template <class T, class... Args>
T from_chars(std::string_view s, Args... args)
{
    const char *end = s.data() + s.size();
    T number;
    auto result = std::from_chars(s.data(), end, number, args...);
    if (result.ec != std::errc{} || result.ptr != end)
        throw std::runtime_error("Cannot convert to number");
    return number;
}

#include <iostream>

int main()
{
    int i = from_chars<int>("42");
    std::cout << i << '\n';
    i = from_chars<int>("1a", 16);
    std::cout << i << '\n';
    float f = from_chars<float>("10.5");
    std::cout << f << '\n';
}

Upvotes: 1

In C++26, you can do:

#include <string_view> // std::string_view
#include <optional> // std::optional
#include <charconv> // std::from_chars

std::optional<int> to_int(const std::string_view string) {
    int result;
    return std::from_chars(string.data(), string.data() + string.size(), result)
        ? result : std::nullopt;
}

thanks to std::from_chars_result’s operator bool().

Upvotes: 2

frank
frank

Reputation: 186

Not sure what you are worrying about. Anyways I did a test for you. For the string length of any int, which fit into small string optimization, the time took to convert a string_view to string: 1.0498 nanoseconds. Even for longer strings that do not fit, it takes: 8.748 nanoseconds.

So just convert it to string and then int probably in a inline function so you have your own "safe standard" way:)

Upvotes: -1

Ron
Ron

Reputation: 15521

The std::from_chars function does not throw, it only returns a value of type from_chars_result which is a struct with two fields:

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

You should inspect the values of ptr and ec when the function returns:

#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.";
    }
}

Upvotes: 41

s3cur3
s3cur3

Reputation: 3025

Building on @Ron and @Holt's excellent answers, here's a small wrapper around std::from_chars() that returns an optional (std::nullopt when the input fails to parse).

#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;
}

Upvotes: 7

Holt
Holt

Reputation: 37651

Unfortunately, there is no standard way that would throw an exception for you but std::from_chars has a return value code that you may use:

#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"};
    }
}

Obviously you can create svtoi, svtol from this, but the advantage of "extending" from_chars is that you only need a single templated function.

Upvotes: 9

Related Questions