Search code examples
c++templatesvariadic-templatesperfect-forwardingcomma-operator

Variadic template argument forwarding uses comma operator


Recently I was learning about C++ and variadic templates. I tried to write a template function that takes a container (I simplified it and I only work with list in this question) and some other arguments and emplaces the other arguments to the container. My code looked like this:

#include <iostream>
#include <list>
#include <utility>

template <typename Container, typename... Args>
void my_emplace(Container &c, Args &&... args){
    c.emplace_back(std::forward<Args>(args)...);
}

int main(void) {
    std::list<int> l = {1, 2};
    my_emplace(l, 3, 4, 5);

    for (auto i : l)
        std::cout << i << ", ";
    return 0;
}

However, the code did not work:

$ g++ -std=c++17 test.cpp
...
/usr/include/c++/7/ext/new_allocator.h:136:4: error: new initializer expression list treated as compound expression [-fpermissive]
  { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I found a solution on the internet -- the line c.emplace_back(std::forward<Args>(args)...); is replaced by (c.emplace_back(std::forward<Args>(args)), ...);. The code works perfectly fine with it.

This is the part that I do not understand. I have never seen this kind of syntax.

  • Why didn't my code work? This answer here basically says that it is correct.
  • What does the correct solution that I found actually do? I've never used a comma operator, but I found that when used, both expressions are evaluated and the result of the first one is discarded. What does it do here and why does the code not work without it?

Thank you for your answers.


Solution

  • Assume args is (1, 2, 3, 4), this call:

    c.emplace_back(std::forward<Args>(args)...);
    

    basically means:

    c.emplace_back(1, 2, 3, 4); so you can't construct int with these arguments to emplace_back hence a compile error.

    And this call:

    (c.emplace_back(std::forward<Args>(args)), ...);
    

    means

    c.emplace_back(1), c.emplace_back(2), c.emplace_back(3), c.emplace_back(4);

    Second call expands the whole expression with comma operator, its called Fold Expressions its added in C++17 you can read more about it Fold Expression