5

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?

1
  • 1
    Things written in double quotes are const char*, not std::string. You need to handle that separately. Commented Sep 7, 2019 at 5:59

3 Answers 3

3

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 <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;
 }
Sign up to request clarification or add additional context in comments.

Comments

2

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

1 Comment

Nice. I like it. This is using a fold expression that comes with C++17.
1

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

Live demo

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.

6 Comments

I had to change mString += Stringify(value) to just Stringify(value) then I got the result I wanted. However, if I just call Stringify() with a std::string parameter I get a compiler error.
@jignatius You are getting error because there is no overload Stringify which takes no args. You can add std::string Stringify() { return ""; } but I don't know if it is what you want, empty string is returned. When all parameters from pack were unpacked Stringify is called with empty list.
Now I get stackoverflow!
@jignatius Check this code, it is simpler version what you want.
That worked. Thanks. So no template specialisation required for std::string?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.