Search code examples
c++templatesc++11overloadingrvalue-reference

template specialization for rvalue and lvalue reference


I play a bit with forwarding and get the following example which works fine.

void Func2( int& a, int& b) { cout << "V1" << endl; }
void Func2( int&& a, int& b) { cout << "V2" << endl; }
void Func2( int& a, int&& b) { cout << "V3" << endl; }
void Func2( int&& a, int&& b) { cout << "V4" << endl; }

    template < typename T, typename U>
void Func( T&& t, U&& u) 
{
    X::Func3( std::forward<T>(t), std::forward<U>(u));
    Func2( std::forward<T>(t), std::forward<U>(u));
}

int main()
{
    int a, b;
    Func( a, b);
    Func( 1, b);
    Func( a, 2);
    Func( 1, 2);

    return 0;
}

But I want also have a function template for Func2 to replace the type int with any type or if not possible a class with specialized methods. The following code fragments will not compile:

class X
{
    public: 
        template < typename T, typename U>
            static void Func3( T& t, U& u) { cout << "X1" << endl; }

        template < typename T, typename U>
            static void Func3( T&& t, U& u) { cout << "X2" << endl; }

        template < typename T, typename U>
            static void Func3( T& t, U&& u) { cout << "X3" << endl; }

        template < typename T, typename U>
            static void Func3( T&& t, U&& u) { cout << "X4" << endl; }
}; 

Results in:

main.cpp: In instantiation of 'void Func(T&&, U&&) [with T = int&; U = int&]':
main.cpp:36:18:   required from here
main.cpp:29:57: error: call of overloaded 'Func3(int&, int&)' is ambiguous
         X::Func3( std::forward<T>(t), std::forward<U>(u));
                                                         ^
main.cpp:29:57: note: candidates are:
main.cpp:9:29: note: static void X::Func3(T&, U&) [with T = int; U = int]
                 static void Func3( T& t, U& u) { cout << "X1" << endl; }
                             ^
main.cpp:12:29: note: static void X::Func3(T&&, U&) [with T = int&; U = int]
                 static void Func3( T&& t, U& u) { cout << "X2" << endl; }
                             ^
main.cpp:15:29: note: static void X::Func3(T&, U&&) [with T = int; U = int&]
                 static void Func3( T& t, U&& u) { cout << "X3" << endl; }
                             ^
main.cpp:18:29: note: static void X::Func3(T&&, U&&) [with T = int&; U = int&]
                 static void Func3( T&& t, U&& u) { cout << "X4" << endl; }
                             ^

Solution

  • As other answers say, the calls are ambiguous because universal references T&&, U&& match both lvalue- and rvalue-references. You can manually remove ambiguities using std::enable_if, e.g.

    template <bool C>
    using only_if = typename std::enable_if <C>::type;
    
    template <typename T>
    using is_lref = std::is_lvalue_reference <T>;
    
    struct X
    {
        template <typename T, typename U>
        static void
        Func3(T& t, U& u) { cout << "X1" << endl; }
    
        template <typename T, typename U>
        static only_if <!is_lref <T>()>
        Func3(T&& t, U& u) { cout << "X2" << endl; }
    
        template <typename T, typename U>
        static only_if <!is_lref <U>()>
        Func3(T& t, U&& u) { cout << "X3" << endl; }
    
        template <typename T, typename U>
        static only_if <!(is_lref <T>() || is_lref <U>())>
        Func3(T&& t, U&& u) { cout << "X4" << endl; }
    };
    

    See also live example. This way you explicitly say T&& should not match an lvalue-reference.

    This approach is hard to generalize to more input arguments. In that case, consider processing only one argument at a time, leaving the remaining ones as rvalue-references. Thus you need only two overloads plus recursive calls, but the exact form depends on what you want to do.