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.