I have a question about forwarding references in member functions of template classes. The standard clearly states (here), that in their example:
template<class T> struct A {
template<class U>
A(T&& x, U&& y, int* p); // x is not a forwarding reference, but y is
};
x is always an rvalue-reference due to T not being a derived but a fully specified class template parameter.
In one of our projects, I stumbled across what I first though was a programming mistake but then found out to be working. So I put together a small code example to test the unexpected behaviour:
#include <iostream>
#include <type_traits>
namespace
{
// Small helper function to output whether the given parameter is an rvalue-reference or not
template < typename TType >
void printType( TType&& value )
{
if( std::is_rvalue_reference< decltype( value ) >::value )
{
std::cout << "rvalue-ref\n";
}
else if( std::is_lvalue_reference< decltype( value ) >::value )
{
std::cout << "lvalue-ref\n";
}
else
{
std::cout << "something else\n";
}
}
template < typename TType >
struct TestStruct
{
// TType&& should be an rvalue-reference since it is a struct-template parameter and not
// deduced
static void func( TType&& value )
{
// forward should always evaluate to std::move
printType( std::forward< TType >( value ) );
}
};
template < typename TType >
void wrapper( TType&& value )
{
TestStruct< TType >::func( std::forward< TType >( value ) );
}
// TType&& is a forwarding reference as it is a deduced template type
template < typename TType >
void func( TType&& value )
{
printType( std::forward< TType >( value ) );
}
}
int main( int argc, char** argv )
{
int a = 2;
// classic rvalue-references with a temporary
func( 2 );
wrapper( 2 );
// classic lvalue-reference with a variable
func( a );
wrapper( a );
// casted rvalue-reference with std::move
func( std::move( a ) );
wrapper( std::move( a ) );
return 0;
}
The main focus lies on the TestStruct struct. The value
parameter clearly should be an rvalue-reference no matter what TType
gets filled with but the output (tested with gcc 7.3.0 and clang 4.0.1-8):
rvalue-ref
rvalue-ref
lvalue-ref
lvalue-ref
rvalue-ref
rvalue-ref
Shows in line 4 that because I derived and forwarded TType
in the wrapper
function, it actually becomes an lvalue-reference in the static member function of TestStruct
. That is somewhat mind-boggling to me as it seems that the property, whether a type is derived or not, is propagated to the class template parameter.
The best explanation I could come up with is, that wrapper
correctly derives TType&&
as lvalue-reference and passes it as such to TestStruct
. In there, I somehow have an rvalue-reference of an lvalue-reference, which I didn't even know was possible. This explanation, however, gets supported by the fact that I get the (expected) compiler error when I write
TestStruct< std::decay_t< TType > >::func( std::forward< TType >( value ) );
in the wrapper function.
What is happening?