Question

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; }
                             ^
Was it helpful?

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.

OTHER TIPS

Following may help:

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

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

So Func3 use really r-value reference instead of universal reference.

This is because T&& is really special in a template function and is not a rvalue reference. It is named universal reference and bind to the kind of the argument. Because of that, several candidate of Func3 result to the same binding, and you end with an ambiguous call.

The code below display "true false true" and show the behavior of universal reference.

#include <iostream>
#include <type_traits>

template <typename T> 
bool foo( T&& v ) {
    return std::is_rvalue_reference<decltype(v)>::value;
}

int main() {
    std::cout << std::boolalpha;
    std::cout << foo( 1 ) << std::endl;
    int a{};
    std::cout << foo( a ) << std::endl;
    std::cout << foo( std::move(a) ) << std::endl;
}

Overloads for const T& and T&& only is not a good solution as T&& will be a better match than const T&. This is different of non template functions.

#include <iostream>
#include <type_traits>

template <typename T> 
bool foo( T&& v ) {
    return false;
}

template <typename T> 
bool foo( T const & v ) {
    return true;
}

bool bar( int&& v ) {
    return false;
}

bool bar( int const & v ) {
    return true;
}

int main() {
    std::cout << std::boolalpha;
    int a{};
    int const b {};
    std::cout << foo( a ) << std::endl; // false
    std::cout << foo( b ) << std::endl; // true

    std::cout << bar( a ) << std::endl; // true
    std::cout << bar( b ) << std::endl; // true
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top