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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With