An awful part of C++17

Author:Wojciech Muła
Added on:2018-03-16
Updated on:2019-05-23 (info about parsing the sign character)

The current state

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

Daydreaming

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.