Search code examples
c++macrosstring-formattingvariadic

variadic macro to generate a vector of strings from a collection of unknown parameters


Currently I am working on a function similar to the String.Format(...) function from C#, just in C++. (String.Format(...))

But that's not my problem. The function works fine but problematic is that it takes a vector<string> as parameter and if I want to use an integer as parameter, I must write code like this:

// function prototype, the function body is not relevant here
string format(string str, vector<string> variables);

// ... some context
  // i could use to_string() here,
  // but imagine a complex type which only overrides the stream operator
  int a = 20; 
  stringstream ss;
  ss << a;
  string a_str = format("a has the value '{}'", { ss.str() });

That's quite some boilerplate code!

Thus I need a function which converts a collection of unknown data types into a vector<string>.

I tried a few things like this:

vector<string> vec_string(vector<void*> args) {
    vector <string> result;

    for (unsigned i = 0; i < args.size(); i++)
    {
        stringstream ss;

        // I can't dereference an object without knowing to pointer type. :(
        ss << *((int*)args[i]);

        result.push_back(ss.str());
    }

    return result;
}

// ... some context

int a = 10;
cout << format("some int: '{}'", vec_string({ (void*) &a }));

Which obviously only works for integer and is very uncomfortable. I feel like the only way to do this is a variadic macro but I got no idea how they work.

here is a link to my format(...) method. I am sorry about my spelling, but I tried my best correcting it.


Solution

  • This can be done relatively easily with variadic templates:

    template <class T>
    auto toString(T&& t) {
        std::stringstream s;
        s << std::forward<T>(t);
        return s.str();
    }
    
    template <class... T>
    auto toStringVector(T&&... args) {
        std::vector<std::string> res {toString(std::forward<T>(args))...};
        return res;
    }
    

    This will convert each parameter to std::string via a stringstream and then return an std::vector<std::string> containing said strings. (Live example.)

    You can then use this straight forward as intended in the question, that is:

    std::cout << format("some text", toStringVector(any, number, of, arguments,
                                          of, any, type));
    

    If you are using Boost, you can skip the toString helper in favor of boost::lexical_cast:

    template <class... T>
    auto toStringVector(T&&... args) {
        std::vector<std::string> res { boost::lexical_cast<std::string>(std::forward<T>(args))...};
        return res;
    }
    

    The lexical_cast will most likely be faster on built-in types.