Author: | Wojciech Muła |
---|---|
Added on: | 2018-03-16 |
Updated on: | 2019-05-23 (info about parsing the sign character) |
I was really happy when saw that C++17 finally introduced standard functions to parse integers and floats. It is a group of functions std::from_chars defined in the header charconv. Unfortunately, it was a fleeting moment of happiness. The proposed API quickly appeared to be awful. Lets look how the integer parser is defined (the floating-point parsers are similar):
struct from_chars_result { const char* ptr; std::errc ec; }; from_chars_result from_chars(const char* first, const char* last, /*integer type*/& value, int base = 10);
The API resembles old good C, with one important exception: it's not good at all. How one is supposed to use it in C++?
#include <string> #include <charconv> // from_chars // ... std::string input; long int result; auto ret = std::from_chars(&*input.begin(), &*input.end(), result); if (ret.ec) { const auto error = std::make_error_code(ret.ec); std::cout << error.message(); }
Yes, to get a char pointer from a string iterator one need to write &*it. The alternative invocation is std::from_chars(input.c_str(), input.c_str() + input.size(), ...). Both are ugly, aren't they?
To make things funnier, from_chars recognizes the minus character, but not the plus character. Yes, it's not a misake — you can convert string "-42" but for "+42" you'll get an error.
Compare this with strtol, which was defined several decades ago:
#include <string> #include <cstdio> #include <cstdlib> // ... std::string input; char* err; long int result = strtol(input.data(), &err, 0); if (errno) { fprintf(stderr, "%s\n", strerror(errno)); }
The function std::from_char is even more cumbersome than strtol. It adds nothing to the existing, established solution. More important fact is that apart from std::from_chars, the recent C++ standard added several pretty useful things, like std::optional, std::variant or std::string_view. What did prevent the committee from using these new facilities?
Why not have a function like this one?
std::optional<int> parse(std::string_view str, int base = 10); // ... if (auto res = parse(input); res.has_value()) { // do sth with res.value() } else { std::err << "'" << input "' is not a valid number"; }
We rarely need a detailed information about an error. Moreover, sometimes we even don't care about possible input errors. So why not allow a programmer to pass a default value? It's a common idiom.
int try_parse(std::string_view str, int def_value, int base = 10); // ... const int value = try_parse(input, 1024*42);
Finally, why the structure returned from std::from_chars is so poor? It might be something like this:
template <typename INTEGER> class parse_result { bool has_value() const; INTEGER value() const; std::exception_ptr get_exception(); operator bool() const { return has_value(); } };
Then using such a result would be more idiomatic:
auto res = parse(input); if (res) { // do sth with res.value() } else { try { std::rethrow_exception(res.get_exception()); } catch (std::exception& e) std::cerr << "Unable to convert '" << input "': " << e.what(); } }
In conclusion: it might have been done way better. The problem has been already solved by many C++ libraries and programming languages; it's nothing new. I still can't find any reasonable answer why such a clumsy, unfriendly API was picked.