Home > Software engineering >  How to generically convert a string to another type using a template function
How to generically convert a string to another type using a template function

Time:09-22

I have a class containing a string member variable and am trying to write a template function that will allow me to convert the string to another type. As a simplified example, without templates this is what I would do.

struct A {
    std::vector<std::string> tokens;
};

void Test(const A& a) {
    // Get the final token value as an int, or default to 42.
    int i = a.tokens.empty() ? 42 : std::stoi(*a.tokens.rbegin());
}

This however has gotten tedious to write in as many places as I'm trying to get the final token and parse it, so I've written the following to assist.

struct A {
    std::vector<std::string> tokens;

    const string& GetLast(const string& defaul = "") {
        return tokens.empty() ? defaul : *tokens.rbegin();
    }
    int GetLast(int defaul) {
        return tokens.empty() ? defaul : std::stoi(*tokens.rbegin());
    }
    float GetLast(float defaul) {
        return tokens.empty() ? defaul : std::stof(*tokens.rbegin());
    }
};

void Test(const A& a) {
    int i = a.GetLast(42);
}

This works fine but it just seems to be asking to be made into a template and allow conversion to any sort of type, provided a converter function is provided. I can't seem to find a generic solution that works, however. The following is the closest thing I can think of that should work, but I constantly get compiler errors that the template types can't be deduced.

template <typename T, typename Converter, typename... Args>
T GetLastAs(T defaul, Args ...args)
{
    return tokens.empty() ? defaul : Converter(*tokens.rbegin(), args...);
}

with usage a.GetLastAs<float, std::stof>(42.0f)

Is there a generic solution to this problem, or is that just wishful thinking on my part?

Edit: I've also gotten close with the following implementation:

template<class Converter, typename T, typename ...Args>
T GetLast(T defaul, Args ...args, Converter f = Converter()) const
{
    return tokens.empty() ? defaul : f(*tokens.rbegin(), args...);
}

with a converter structure defined as struct ToInt { int operator()(const std::string& str) { return std::stoi(str); } }; and a usage of a.GetLast<ToInt>(42); or a.GetLast(42, ToInt()); But I cannot replace the ToInt in the usage to std::stoi and I believe the reason may be because stoi has an overload to take wstring instead of string and the GetLast method cannot figure out which overload to use. I don't know if it's even possible to specify which overload to use.

CodePudding user response:

You can use a stringstream to extract values of default constructible types like this:

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

template <typename T>
T GetAs(const std::string& s) {
    std::stringstream ss{s};
    T t;
    ss >> t;
    return t;
}

int main() {
    std::string s{"3.5"};
    std::cout << GetAs<int>(s) << "\n";
    std::cout << GetAs<double>(s) << "\n";
    std::cout << GetAs<std::string>(s) << "\n";    
}

I put the default and the fact that the string is element of a vector aside, because that isnt essential for the solution.

CodePudding user response:

You could put the string into an std::istringstream and extract the value into a variable using the >> operator.

As in

T value{};
std::istringstream{ string_to_convert } >> value;

Of course, this requires T to be default-constructible, as well as there being an >> operator defined for T.


IIRC this is how Boost lexical cast works (or at least used to work). If you already using Boost in your project, I suggest you use its lexical cast library instead of making your own.

Or for more generic conversion (if you're using Boost) Boost convert. Which can handle different numeric bases.

  • Related