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?
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;
}