Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perfect-forwaring of the variadic template parameters of a struct

In my C++11 code, I have a variadic struct and a function that should use perfect-forwarding for the struct's variadic types such as this:

template <typename... T>
struct S
{
    void X(T&&... args)
    {
        Do(std::forward<T>(args)...);
    }
};

Just assume that Do is a free-standing variadic function. Given a type struct V { int x, y; }; I want to call S::X like so:

S<V> s;
V v = { 1, 2 };

s.X(V());   // Compiles
s.X(v);     // Does not compile

The last line produces the following error in Visual Studio 2013 and Visual Studio 2013 with the November 2013 CTP of the C++ compiler:

error C2664: 'void S<V>::X(V &&)' : cannot convert argument 1 from 'V' to 'V &&'
You cannot bind an lvalue to an rvalue reference

I tried mingw 4.8.1 and got a similar error, so it doesn't seem to be a compiler or C++11-support problem:

Source.cpp:51:7: error: cannot bind 'V' lvalue to 'V&&'
  s.X(v);  // error C2664: 'void S<V>::X(V &&)' : cannot convert argument 1 from 'V' to 'V &&'
   ^
Source.cpp:17:7: error:   initializing argument 1 of 'void S<T>::X(T&& ...) [with T = {V}]'
  void X(T&&... args)
   ^

I was surprised to find out that a call to s.X(v) wouldn't work, as that is what universal references and perfect-forwarding are all about, right? While trying to figure out what's going on, I first noticed that it does indeed work when X is a free-standing variadic function, and it also works if I change X to be 'doubly variadic', in a sense:

template <typename... T>
struct S
{
    template <typename... T2>
    void X(T2&&... args)
    {
        Do(std::forward<T2>(args)...);
    }
};

Now, calling both s.X(v) and s.X(V()) work as expected, however, the relationship between the T and T2 variadic template arguments is now unclear. Reading this stackoverflow question, I get the impression that the original version of X is in fact not using perfect-forwarding and universal references at all; instead, when S's template arguments are expanded by the compiler, the definition of S::X is also expanded, hence the function's prototype is actually void S::X(V&& v), in which case the error message makes perfect sense.

Can anyone confirm this behavior? Is there a way to have a true perfect-forwarding/universal reference function within a variadic struct without repeating the variadic arguments? If my suspicion is indeed correct, is that a defect of the current C++ standard?

like image 361
Axel Habermaier Avatar asked Jan 07 '14 13:01

Axel Habermaier


2 Answers

In your first example :

template <typename... T>
struct S
{
    void X(T&&... args)
    {
        Do(std::forward<T>(args)...);
    }
};

T is evaluated to V when you declare S<V> s, therefore the only prototype of the generated X member function is :

void X(V&&);

Whereas in your second example :

template <typename... T>
struct S
{
    template <typename... T2>
    void X(T2&&... args)
    {
        Do(std::forward<T2>(args)...);
    }
};

T is evaluated to V& when you call it with s.X(v) so the generated prototype of the member function X is :

void X(V& &&);

which becomes (with reference colapsing) :

void X(V&);

To answer your second question, you either need to repeat the variadic template parameters or use function overloading.

like image 85
Drax Avatar answered Sep 19 '22 13:09

Drax


You're correct: Universal references (and thus perfect forwarding) only work when the parameter is of type T&& for a deduced T. In your original case, the T comes from the struct's tempalte parameters, so it's not deduced.

I can't comment on wether it's a defect in the current standard, but consider this: perfect forwarding works by sometimes (in very specific cases) using U& instead of U for deducing T (where U is the actual argument type). That's not possible when the template parameter is already "fixed" by being the parameter of the class.

like image 37
Angew is no longer proud of SO Avatar answered Sep 19 '22 13:09

Angew is no longer proud of SO