3

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 1s (possibly doing some side effects, if bar() was designed as such). Why does this behave differently? Where is my logic flawed?

Fureeish
  • 12,533
  • 4
  • 32
  • 62
  • `void()` isn't a function call, it's a cast. – David G Jan 09 '20 at 00:55
  • 1
    @0x499602D2 I believe I didn't state that it's a function call. I used it to make `void(std::forward(ts)...);` compile. Just stuck with it afterwards. When I mention function calls, I mean the `bar()` and `std::forward()` calls. – Fureeish Jan 09 '20 at 00:58
  • *"Parameter pack expansion, used with a function call..."* for `void(bar(args)...)` the expansion doesn't occur within the function call for `bar` it happens inside of `void()`, which is not a function. If there's more than one argument it will fail. – David G Jan 09 '20 at 01:00
  • 1
    @0x499602D2 "*parameter pack expansion*" - `...` "*used with a function call*" - `bar(args)`. If you think that I should phrase that differently here, I will gladly take my chance to clarify the intiention and edit the question. – Fureeish Jan 09 '20 at 01:02
  • @0x499602D2 *"If there's more than one argument it will fail*" - that would mean that my assumtion is incorrect in the case of `void(std::forward(ts)...);`. The expansion does not evaluate to a list of comma-separated, forwarded arguments? – Fureeish Jan 09 '20 at 01:04
  • There's another feature that you're using when doing `(bar(args), ...)`. I forget what it's called though. – David G Jan 09 '20 at 01:14
  • 2
    `void(std::forward(ts)...);` [does not in fact compile](https://godbolt.org/z/UZAnZw). The term you are looking for is [fold expression](https://en.cppreference.com/w/cpp/language/fold) – Igor Tandetnik Jan 09 '20 at 01:19
  • @IgorTandetnik oh snap, it seems that I must've either omitted the call when I tested the samples or I screwed up somewhere else. Will make an according edit, thank you. – Fureeish Jan 09 '20 at 01:22
  • 1
    @Fureeish: `(args + ...)` won't have comma at all in expansion. – Jarod42 Jan 09 '20 at 08:45

1 Answers1

7

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

Well, there's your problem: that's not how it works. Or at last, not quite.

Parameter pack expansions and their nature are determined by where they are used. Pack expansions, pre-C++17, can only be used within certain grammatical constructs, like a braced-init-list or a function call expression. Outside of such constructs (the previous list is not comprehensive), their use simply is not allowed. Post-C++17, fold expressions allow them to be used across specific operators.

The reason for this is in part grammatical. Consider this: bar(1, (2, 3), 5). This calls the function with 3 arguments; the expression (2, 3) resolves down to a single argument. That is, there is a difference between the comma used in an expression and the comma used as a separator between values to be used in a function call. This difference is made at the grammatical level; if I want to invoke the comma operator in the middle of a sequence of function arguments, I have to put that whole thing in () so that the compiler will recognize the comma as a comma expression operator, not a comma separator.

Non-fold pack expansions effectively expand to use the separation comma, not the expression comma. As such, they can only be expanded in places where the separation kind of comma is valid.

The reason (bar(args)...) doesn't work is because a () expression cannot take the second kind of comma.

Barry
  • 286,269
  • 29
  • 621
  • 977
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • That's put beautifully, thank you very much. Do you have any good sources worth recommending (other than, well, the standard) where can I learn more in detail about those contexts and templates? I know it's off-topic for StackOverflow to ask for such recommendations in questions, but I believe a little comment could slip through. I had my eyes on Josuttis' `C++ Templates, The Complete Guide`, but didn't order the book yet. – Fureeish Jan 09 '20 at 01:40