53

In C++ standard library there are functions to convert from string to numeric types:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

but I find it tedious to use them in template code. Why there are no template functions something like:

template<typename T>
T sto(...)

to convert strings to numeric types?

I don't see any technical reason to not have them, but maybe I'm missing something. They can be specialised to call the underlying named functions and use enable_if/concepts to disable non numeric types.

Are there any template friendly alternatives in the standard library to convert string to numeric types and the other way around in an efficient way?

Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
  • Does this answer your question? [Why is the \`std::sto\`... series not a template?](https://stackoverflow.com/questions/37920924/why-is-the-stdsto-series-not-a-template) – Boiethios Feb 20 '20 at 16:31
  • 1
    @Boiethios not really - the answers from that question explain the rationale behind the "why", but they don't come with practical solutions like the accepted answer. I've edited my question to ask for alternative to better state what I need – Mircea Ispas Feb 24 '20 at 06:43

5 Answers5

43

Why there are no template functions something like:

C++17 has such generic string to number function, but named differently. They went with std::from_chars, which is overloaded for all numeric types.

As you can see, the first overload is taking any integer type as an output parameter and will assign the value to it if possible.

It can be used like this:

template<typename Numeric>
void stuff(std::string_view s) {
    auto value = Numeric{};

    auto [ptr, error] = std::from_chars(s.data(), s.data() + s.size(), value);

    if (error != std::errc{}) {
        // error with the conversion
    } else {
        // conversion successful, do stuff with value
    }
}

As you can see, it can work in generic context.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • 5
    C++ has destructuring now? :o [Structured binding declaration](https://en.cppreference.com/w/cpp/language/structured_binding) – Alexander Feb 20 '20 at 15:19
  • 1
    Of course! It even work with simple structs or if given the right interface, classes too. – Guillaume Racicot Feb 20 '20 at 15:21
  • It is worth noting that this won't work with floating point precision, as mentioned on [Reddit](https://www.reddit.com/r/cpp/comments/ankc50/when_will_gcc_have_stdfrom_chars_for_floating) and as I just tested by myself. – Patrizio Bertoni Dec 02 '20 at 11:22
  • @PatrizioBertoni as far as I know it's supposed to work with a C++17 complete standard library. MSVC implement the floating point version of the functions. – Guillaume Racicot Dec 02 '20 at 18:31
15

It's not a template, and it doesn't work with locales but if that isn't a show stopper then C++17 already has what you want: std::from_chars

There are overloads for all of the integer and floating-point types and the interface is the same except for the last parameters which are different for the integer and floating-point types respectively (but if the default is fine then you don't need to change anything). Because this is not a locale-aware function it is also quite fast. It will beat any of the other string to value conversion function and generally it is by orders of magnitude.

There is a very good CPPCON video about <charconv> (the header from_chars lives in) by Stephan T. Lavavej that you can watch about its usage and performance here: https://www.youtube.com/watch?v=4P_kbF0EbZM

Oleg Om
  • 429
  • 5
  • 15
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 1
    @NathanOliver: `stoi` and its friends (the conversions mentioned in the question) also don't work with locales, so that isn't a showstopper. – Pete Becker Feb 19 '20 at 14:35
11

You wouldn't gain much because in an expression like

int x = sto("1");

There is no (easy) way to deduce the desired type for the template parameter. You would have to write

int x = sto<int>("1");

which to some extend defeats the purpose of providing a generic function. On the other hand, a

template<typename T>
void sto(std::string x,T& t);

would be of good use as you realized. In C++17 there is std::from_chars, which does more or less exactly that (it is no template but a set of overloads and it takes pointers to chars instead of a string, but thats only minor details).

PS There is no easy way to deduce the desired type in the above expression, but there is a way. I dont think the core of your question was exactly the signature you asked for, and I dont think the following is a good way to implement it, but I knew that there is a way to make the above int x = sto("1"); compile and I was curious to see it in action.

#include <iostream>
#include <string>

struct converter {
    const std::string& x;
    template <typename T> operator T() { return 0;}
};

template <> converter::operator int() { return stoi(x); }
template <> converter::operator double() { return stod(x); }
converter sto(const std::string& x) { return {x}; }

int main() {
    std::string s{"1.23"};
    int x = sto(s);
    double y = sto(s);
    std::cout << x << " " << y;
}

This works as intended, but it has severe downsides, maybe most importantly it allows to write auto x = sto(s);, ie it is easy to use wrong.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • I think relying on the implicit conversion here is a good idea. Trying to disable auto is a problem though. Typically, I have seen that done by putting a private const reference in a class which is only initialized by valid methods. I can't see how one would leverage that here though because we kind of have to construct a whole converter object before proceeding. Hmmm.... – bremen_matt Feb 20 '20 at 08:13
  • I can see value despite the non-deduced type parameter - as the question says, the motivation is to be able to use from within template code, where you're converting to a type that varies between instantiations. – Toby Speight Feb 20 '20 at 11:59
  • What's the fundamental problem with `auto x = sto(s)` ? This particular implementation breaks because `converter::x` is a reference which goes out of scope, but that's fixable. Just remove the reference and rely on `std::string`'s move semantics. – MSalters Feb 20 '20 at 16:15
  • @MSalters yes, it was the reference that I thought is problematic, but you are right, no need to use a reference. What actually disturbs me more is that it appears to be a function but the actual functionality is in `converter`, also I am not sure if using a template conversion operator was the best choice, things that could be fixed. Maybe it isnt that bad as I initially thought – 463035818_is_not_an_ai Feb 20 '20 at 16:20
  • I don't think there is any problem with the const reference here. My understanding is that the const reference will preserve the lifetime of the string until the converter is destructed (https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/) – bremen_matt Feb 20 '20 at 19:30
  • So holding a plain reference to a temporary string would be an issue, but holding a const reference is no problem because the const reference will extend the lifetime. – bremen_matt Feb 20 '20 at 19:36
5

Solution compatible with all (even older C++ compilers like C++-98 ones) is to use boost::lexical_cast which is a template to convert between numeric and string types in both ways.

Example:

short myInt = boost::lexical_cast<short>(*argv);
std::string backToString = boost::lexical_cast<std::string>(myInt);

See: https://www.boost.org/doc/libs/1_42_0/libs/conversion/lexical_cast.htm

Łukasz Ślusarczyk
  • 1,775
  • 11
  • 20
4

On older C++ versions, stringstream is your friend. If I understand correctly, then the following might be interesting to you. It is C++11.

https://wandbox.org/permlink/nUNiUwWWTr7a0NXM

#include <sstream>
#include <string>
#include <iostream>

template<typename T, typename String>
T sto(const String & str) {
    T val;
    std::stringstream ss(str);
    ss >> val;
    return val;
}

template<typename T, typename String>
void sto(const String & str, T & val) {
    std::stringstream ss(str);
    ss >> val;
}

int main() {   
    std::cout << sto<float>("1.1") << ", " << sto<int>(std::string{"2"});

    // An alternative version that infers the type 
    double d;
    sto("3.3", d);
    std::cout << ", " << d;
}

This method works in C++11 and is pretty general. In my experience, this method is robust, but not the most performant.

bremen_matt
  • 6,902
  • 7
  • 42
  • 90