Migrating towards from_chars()

Obtaining a floating-point or integral value from a string is so often necessary that there are more than one ways of doing this due to the history of C++ (and C). Before we look at the most modern (and best!) let’s consider all of the ways that modern C++ has inherited or developed for performing this task:

1 – atoi, atof

These are C-library functions which accept a pointer to a null-terminated multi-byte string (NTMBS) and return an integral or floating-point type. Any trailing non-numeric characters are ignored, as is preceding and trailing whitespace. Invalid input causes a zero-value number to be returned.

auto i = atoi("-123");
auto d = atof("1.23e4"); // d is in fact a double

2 – strtol, strtod

Again these are C-library functions which accept a pointer, and also an address of a second pointer used to return the point at which the read ended (passing nullptr is also valid if this information is not required). For integral numbers a base needs to be specified.

auto l = strtol("1234", nullptr, 10); // l is of type long
const char *s = "1.234e-5 hello";
char *p;
auto d = strtod(s, &p);
cout << "d = " << d << "\np = \"" << p << "\"\n" ;

Output:

d = 1.234e-05
p = " hello"

3 – stoi, stod

These C++ Standard Library functions didn’t appear until C++11 and convert from a std::string (or std::wstring). Optionally, the address of a size_t variable can be given to return the index at which reading stopped, and also the base to be used for reading integers.

auto i = stoi("123"s);
size_t idx;
auto d = stod("1.234e5 hello"s, &idx);

Interestingly these are just convenience wrapper functions around strtol etc. and so involve a slight performance overhead. A base can be provided for integral conversions, if required.

4 – istringstream

The nice thing about C++ string streams is their user-friendliness as they have the same syntax as when using cin. Unfortunately they come with a performance hit and so are not suitable for use in performance code, and error recovery can be cumbersome.

istringstream iss{ "123 1.234e5 hello" };
int i; double d; string s;
iss >> i >> d >> s;

Introducing from_chars()

The from_chars functions were introduced with C++17 and the Standard Library contains overloads for all signed and unsigned integral types, plus all three built-in floating-point types. The syntax is very similar for all of these overloads. Despite operating exclusively on const char * values they are particularly suitable for use with string_views. The return type of these functions is a struct with two fields: a const char * pointer to the first unread character, and an error code of type std::errc. With Modern C++ we can use aggregate initialization on this return value.

string_view n1{ "123" }, n2{ "1.234e-5 hello" };

int i;
from_chars(n1.data(), n1.data() + n1.size(), i);
cout << "i = " << i << '\n';

double d;
auto [ p, e ] = from_chars(n2.data(), n2.data() + n2.size(), d);
if (e == errc()) { // check no error was encountered
    cout << "d = " << d << ", p = \"" << p << "\"\n";
}

Output:

i = 123
d = 1234e-05, p = " hello"

As can be seen from the above code, the return values of from_chars can be easily discarded, however checking the error field is good coding practice. Possible error codes which can be returned are std::errc::invalid_argument (for zero length input), or std::errc::result_out_of_range. A piece of good news about from_chars is that under certain conditions it is the quickest of all of these conversion methods, so there is no reason to not use it. In fact, you should modernize code you encounter which is still using any of the other methods listed above.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s