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_view
s. 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.