Reputation: 6504
I'm new to variadic template functions. I have written a simple class, StringStream
, that has a variadic template function that creates a std::string
from variable template arguments - strings, ints, etc.
#include <string>
#include <sstream>
class StringStream
{
public:
StringStream() = default;
~StringStream() = default;
template<typename T>
std::string Stringify(const T &value)
{
mStream << value;
return mStream.str();
}
template<typename T, typename... Ts>
std::string Stringify(const T& value, Ts... values)
{
mStream << value;
return Stringify(values...);
}
private:
std::stringstream mStream;
};
What I want to do now is use a std::string
member in StringStream
instead of std::stringstream
and build the string from the arguments of Stringify()
. For arguments that are not std::string
I want to convert to strings with std::to_string()
, otherwise I just concatenate the argument. I am running into a compiler error. Here's my modified class:
class StringStream
{
public:
StringStream() = default;
~StringStream() = default;
template<typename T>
std::string Stringify(const T &value)
{
mString += std::to_string(value);
return mString;
}
template<>
std::string Stringify<std::string>(const std::string& value)
{
mString += value;
}
template<typename... Ts>
std::string Stringify(const std::string& value, Ts... values)
{
mString += value;
return Stringify(values...);
}
template<typename T, typename... Ts>
std::string Stringify(const T& value, Ts... values)
{
mString += std::to_string(value);
return Stringify(values...);
}
private:
std::string mString;
};
My compiler error says:
error C2665: 'std::to_string': none of the 9 overloads could convert all the argument types
I am calling the function like this:
int main()
{
int age;
std::cin >> age;
StringStream ss;
std::cout << ss.Stringify("I", " am ", age, " years ", "old") << std::endl;
}
Is there any way to resolve this?
Upvotes: 5
Views: 3955
Reputation: 9045
You should continue to use stringstream
, concatenating strings using +
is inefficient, since you copy the entire string for every +
operation.
Just convert the result to a string before your function exits. You don't need an instance of a class.:
MWE:
#include <iostream>
#include <sstream>
template<typename... TArgs>
std::string Stringify(const TArgs&... args) {
std::stringstream ss;
(ss << ... << args);
return ss.str();
}
int main()
{
int age;
std::cin >> age;
std::cout << Stringify("I", " am ", age, " years ", "old") << std::endl;
}
Upvotes: 2
Reputation: 66230
The reason of the error is that, string literals ("I"
, " am "
, " years "
, "old"
) are arrays of constant char
s (char const [N]
, for some N
). You can intercept they as char const *
but not as std::string
.
A little off topic, I suppose, but I give you two suggestions:
(1) divide Stringify()
in two function: the variadic one, public
, that call a private
one (toStr()
, in my following example) to make conversion over singles arguments
(2) avoid recursion for the variadic version of Stringify()
but simply use pack expansion.
I mean... you can write Stringify()
as follows
template <typename... Ts>
std::string Stringify (Ts const & ... vals)
{
using unused = int[];
(void)unused { 0, (mString += toStr(vals), 0)... };
return mString;
}
or, if you can use C++17, using template folding
template <typename... Ts>
std::string Stringify (Ts const & ... vals)
{ return ((mString += toStr(vals)), ...); }
For toStr()
, I propose a template version that uses std::to_string()
but enabled only when the template T
type isn't convertible to std::string
template <typename T>
typename std::enable_if<
false == std::is_convertible<T, std::string>::value,
std::string>::type toStr (T const & val)
{ return std::to_string(val); }
and the non-template version that accept a std::string
std::string toStr (std::string const & val)
{ return val; }
This way, if an argument is directly convertible to std::string
(is std::string
or another type that can be used to construct a std::string
) the non-template version is called; otherwise is called the template one.
The following is a full compiling example
#include <iostream>
#include <type_traits>
class StringStream
{
private:
std::string mString;
template <typename T>
typename std::enable_if<
false == std::is_convertible<T, std::string>::value,
std::string>::type toStr (T const & val)
{ return std::to_string(val); }
std::string toStr (std::string const & val)
{ return val; }
public:
StringStream() = default;
~StringStream() = default;
template <typename... Ts>
std::string Stringify (Ts const & ... vals)
{
using unused = int[];
(void)unused { 0, (mString += toStr(vals), 0)... };
return mString;
}
};
int main ()
{
int age = 42;
StringStream ss;
std::cout << ss.Stringify("I", " am ", age, " years ", "old") << std::endl;
}
Upvotes: 3
Reputation: 20969
You are calling to_string
inside
template<typename T>
std::string Stringify(const T &value)
{
mString += std::to_string(value);
return mString;
}
so you can remove to_string
from Stringify(const T& value, Ts... values)
, and replace it by just Stringify
:
template<typename T, typename... Ts>
std::string Stringify(const T& value, Ts... values)
{
mString += Stringify(value);
return Stringify(values...);
}
add also specialization for const char*:
std::string Stringify(const char* value)
{
return mString += value;
}
Now your problem is that const char[2]
is passed to_string
, but to_string
doesn't have overload which accepts that input. By replacing to_string
by Stringify
an appropriate overload can be used for first argument from arguments pack.
Upvotes: 1