Tuesday 5 December 2017

c++ - Variadic template function to create string

itemprop="text">


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

#include

class
StringStream
{
public:
StringStream() =
default;
~StringStream() = default;



template
std::string Stringify(const T
&value)
{
mStream << value;
return
mStream.str();
}

template 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
std::string Stringify(const T
&value)

{
mString +=
std::to_string(value);
return mString;
}


template<>
std::string Stringify(const
std::string& value)
{
mString += value;

}


template
std::string
Stringify(const std::string& value, Ts... values)
{
mString +=
value;
return Stringify(values...);
}


template
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?


class="post-text" itemprop="text">
class="normal">Answer



The reason
of the error is that, string literals ("I", " am
"
, " years ", "old") are
arrays of constant chars (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             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

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 std::enable_if<
false ==
std::is_convertible::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

#include

class
StringStream
{
private:

std::string
mString;

template
typename
std::enable_if<
false == std::is_convertible 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

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;

}

No comments:

Post a Comment

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print &q...