2

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?

Eggcellentos
  • 1,570
  • 1
  • 18
  • 25
Baldurius
  • 21
  • 1
  • Does that mean my explanation is correct? But if `TType` is an rvalue of an lvalue, why is printType recognizing it as an lvalue even though I forwarded it? – Baldurius Feb 15 '18 at 09:46
  • 2
    `// TType&& should be an rvalue-reference`. that comment is wrong for `TestStruct` (reference collapsing). – Jarod42 Feb 15 '18 at 09:48

0 Answers0