0

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.

Chris
  • 325
  • 1
  • 3
  • 11

2 Answers2

1

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.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
1

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Would using stringstreams allow me to convert to arbitrary types (provided I have a converter function), like if I wanted to convert a string of `1,2,3` to an `array` or `glm::vec3`? I'm assuming I'd need to write my own >> operator overload. – Chris Sep 18 '21 at 22:05
  • As a separate question, `stof` and `stoi` provides additional parameters for specifying the base of the number represented in the string. How would I specify extra parameters when it gets converted to a float or an int? This is why I originally had a variadic template in my example. – Chris Sep 18 '21 at 22:07
  • @Chris I dont quite understand what you mean with "converter function". Yes there must be a `std::istream& operator>>(std::istream&,T&)` to make `ss >> t;` work. I would not recommend to overload `operator>>` for `std::array` (at least not directly) though. I didnt consider this because the question only mentions conversions of the `stoX` family – 463035818_is_not_an_ai Sep 18 '21 at 22:44
  • @Chris btw the confusing thing about the `Converter` in your question is that you say it is a function but in your code it is a type. `Converter(*tokens.rbegin(), args...)` is a constructor call (and `Converter` would need to be convertible to `T` to make your code work) – 463035818_is_not_an_ai Sep 18 '21 at 22:46
  • @Chris hm... depends on what you actually want. If you merely want to convert to `float` and `int` then there is no reason to make it a template. For a (small) limited set of types, overloads are usually the easier alternative. What conversions do you want to enable? – 463035818_is_not_an_ai Sep 18 '21 at 22:49
  • I was basing the converter function off of [this](https://stackoverflow.com/a/2156899) answer where a function is passed as a template argument. I thought if I can generically pass a function to the template then it wouldn't matter whether it was a stoX function or my own struct converter class. – Chris Sep 18 '21 at 23:40
  • @Chris you can pass `stoi` as non-type template parameters (like in the answer you link), though in your code `Converter` is a type – 463035818_is_not_an_ai Sep 19 '21 at 17:31