Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic template queries

I am trying to understand below code. Copied directly from Jason Turner youtube video

#include <iostream>
#include <sstream>
#include <vector>

template<typename ...T>
std::vector<std::string> print(const T& ...t)
{
    std::vector<std::string> retval;
    std::stringstream ss;
    (void)std::initializer_list<int>{
      (
         ss.str(""),
         ss << t,
         retval.push_back(ss.str()),
         0)...
    };
    return retval;
}

int main()
{
    for( const auto &s : print("Hello", "World", 5.4, 1.1, 2.2) ) {
        std::cout << s << "\n";
    }
}

Questions :

  1. Can someone give the expanded view of the code within initializer_list? I am having hard time visualising how the statement expands per argument? Does the ss.str(""), ss << t and then the push_back happen for each parameter in the pack OR they are just executed once? I am not able to visualize how the expanded initializer list will look like?
  2. Why do we need the dummy '0' at the end of the initializer_list? What happens if i don't have that?
  3. How can i easily view the ... expansion in code i shared?
like image 585
TestUser Avatar asked Jul 17 '26 00:07

TestUser


1 Answers

T and t are parameter packs.

There are two primary ways of using a pack: a fold expression (in C++17 and newer) and just a regular pack expansion.

A fold expression would look like this:

((ss.str(""), ss << t, retval.push_back(ss.str())), ...);

Fold expression repeats its operand for each pack element, inserting some operator (, in this case) between parts belonging to each argument. The one above expands to:

((ss.str(""), ss << t1, retval.push_back(ss.str())), // <-- Inserted commas
 (ss.str(""), ss << t2, retval.push_back(ss.str())), // <--
 (ss.str(""), ss << t3, retval.push_back(ss.str())));

A regular expansion is similar, except that it always generates a comma, and that comma can not be an operator (as opposed to e.g. a separator between array initializers, or function arguments).

E.g. if you wrote (ss.str(""), ss << t, retval.push_back(ss.str()))...; under the assumption that it would work like that fold expression, it wouldn't work, because the resulting comma would have to be an operator.

Because of this limitation, before C++17 people were using dummy arrays (or initializer_lists like in your example). Here's how it would look with an array:

int dummy[] = {(ss.str(""), ss << t, retval.push_back(ss.str()), 0)...};

This expands to:

int dummy[] = {(ss.str(""), ss << t1, retval.push_back(ss.str()), 0),
               (ss.str(""), ss << t2, retval.push_back(ss.str()), 0),
               (ss.str(""), ss << t3, retval.push_back(ss.str()), 0)};

Here, the size of the array (or initializer_list) matches the size of the pack.

,0 is necessary because each array element is an int, so it must be initialized with an int.

like image 131
HolyBlackCat Avatar answered Jul 19 '26 13:07

HolyBlackCat