I wanted to better understand parameter pack expansions, so I decided to research a bit and, what once seemed obvious to me, stopped being so obvious after trying to understand what exactly is going on. Let's examine a standard parameter pack expansion with std::forward
:
template <typename... Ts>
void foo(Ts&& ... ts) {
std::make_tuple(std::forward<Ts>(ts)...);
}
My understanding here is that for any parameter pack Ts
, std::forward<Ts>(ts)...
will result in a comma-separated list of forwarded arguments with their corresponding type, e.g., for ts
equal 1, 1.0, '1'
, the function body will be expanded to:
std::make_tuple(std::forward<int&&>(1), std::forward<double&&>(1.0), std::forward<char&&>('1'));
And that makes sense to me. Parameter pack expansion, used with a function call, results in a comma-separated list of calls to that function with appropriate arguments.
What seems to be bothering me is why then would we sometimes need to introduce the comma operator (operator,
), if we want to call a bunch of functions in a similar manner? Seeing this answer, we can read this code:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args) {
(bar(args), ...); // <- notice: comma here
}
int main() {
foo2(1, 2, 3, "3");
return 0;
}
followed by information that it will result in the following expansion:
(bar(1), bar(2), bar(3), bar("3"));
Fair, makes sense, but... why? Why doing this, instead:
template<typename... Args>
static void foo2(Args... args) {
(bar(args)...); // <- notice: no comma here
}
doesn't work? According to my logic ("Parameter pack expansion, used with a function call, results in a comma-separated list of calls to that function with appropriate arguments"), it should expand into:
(bar(1), bar(2), bar(3), bar("3"));
Is it because of bar()
returning void
? Well, changing bar()
to:
template<typename T>
static int bar(T t) { return 1; }
changes nothing. I would imagine that it would just expand to a comma-separated list of 1
s (possibly doing some side effects, if bar()
was designed as such). Why does this behave differently? Where is my logic flawed?