2

In this answer I have seen some C++11 code, that I don't really understand (but I would like to).

There, a variadic template function is defined, that (maybe?) takes all arguments passed and inserts them into a std::ostringstream.

This is the function (see the complete working example at the linked answer):

template<typename T, typename... Ts>
std::string CreateString(T const& t, Ts const&... ts)
{
    using expand = char[];

    std::ostringstream oss;
    oss << std::boolalpha << t;
    (void)expand{'\0', (oss << ts, '\0')...};
    return oss.str();
}

When I had to guess, I'd say, a character array is created and initialized with \0-bytes, and the insertion of the function's arguments into the stream happens as a side effect of the initialization (part of that comma expression). In the end, the array contains as many nulls as items were inserted into the stream. It's casted to void in order to avoid a compiler warning (unused variable). Similar to this:

char arr[] = {'\0', (oss << t1, '\0'), (oss << t2, '\0'), ..., (oss << tn, '\0')};

Is my attempt to describe this function's workings accurate? Can someone explain what design decision might be relevant here, and why is it beneficial to implement it this way (the alternative would be compile-time recursion, I guess)?

Community
  • 1
  • 1
moooeeeep
  • 31,622
  • 22
  • 98
  • 187
  • 6
    The `'\0'` is there just in case the `Ts` is empty, so the init won't fail. But other than that, everything you're saying is correct. – vsoftco Feb 05 '16 at 20:21
  • 1
    One advantage is that you aren't introducing any additional frames on the call stack. – AndyG Feb 05 '16 at 20:36
  • 1
    And generally speed up compilation as there is only one generated function instead of N. – Jarod42 Feb 05 '16 at 20:44
  • @vsoftco How would the initialization fail? Can't the initializer list be empty? – moooeeeep Feb 05 '16 at 20:48
  • 1
    Note that the alternative in c++17 would be `(oss << ... << ts);` – Jarod42 Feb 05 '16 at 20:48
  • 3
    @moooeeeep: An empty initializer list is legal, not the case for an empty array. – Jarod42 Feb 05 '16 at 20:48
  • One little nit: this is not unused variable, this is constructing temporary array without assigning it anywhere. Compilers would normally warn at such constructs. i.e: things like: `MyType{parameters}; ` may cause warnings as they just construct temporary MyType object. – seb Feb 08 '16 at 21:59

1 Answers1

3

After receiving some helpful hints in the question's comment section I could figure out some of the points:

  • The feature being used is called pack expansion. It works exactly as described in the OP: a temporary array is initialized with the side effect of stream insertion. A detailed description of the feature can be found here:
  • This is a work-around that is necessary to avoid the recursion approach, which would be less efficient. In C++17 this work-around is no longer needed, because of the then introduced fold expressions:
  • The first array element is initialized \0 to avoid the program being ill-formed if the function was called with only one argument. Else, the array object would be created empty, with an incomplete type (array of unknown bounds), which is illegal. For further reading:
Community
  • 1
  • 1
moooeeeep
  • 31,622
  • 22
  • 98
  • 187