Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloaded function templates with reference parameters

template <typename T> void f(T&) {}
template <typename T> void f(T&&) {}
int main()
{
    int x;
    f(x);   //ambiguous
}

Why is this call ambiguous? The first template specialization is f<int>(int&), and the second is f<int&>(int&). As the parameters are the same, the function template, which is more specialzed according to the partial ordering rules, is better. Then according to Standard 14.8.2.4/9

If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transformations above) and both P and A were reference types (before being replaced with the type referred to above):
— if the type from the argument template was an lvalue reference and the type from the parameter template was not, the argument type is considered to be more specialized than the other; ...

The first template has T& and the second has T&&, so the first should be more specialized. What is wrong here?


Edit: This code is tested in g++ 4.6.1 and VC++ 2010 Express, both give the ambiguous error.

like image 933
Cosyn Avatar asked Jan 18 '23 02:01

Cosyn


1 Answers

Guideline:

Do not overload:

template <typename T> void f(T&) {}
template <typename T> void f(T&&) {}

Reason:

There is a special template deduction rule for the pattern:

template <typename T> void f(T&&) {}

This rule exists in order to enable so called "perfect forwarding". It helps things like bind and make_shared forward their arguments perfectly, preserving both cv-qualifiers and "value category" (lvalue/rvalue-ness).

This special rule says that when f(T&&) is called with an lvalue parameter (e.g. int), that T gets deduced as an lvalue reference (e.g. int&) instead of int. And an rvalue reference to lvalue reference to int collapses down to just lvalue reference to int. I.e.

f(x)

calls

f<int&>(int& && x);

which simplifies to:

f<int&>(int& x);

Edit

This is not more or less specialized than f<int>(int&).

Thanks to Johannes Schaub for the correction (see comments).

Solution:

You can do whatever you want with the single function:

template <typename T> void f(T&&) {}

If T deduces as an lvalue reference, do whatever you wanted to do in your first overload, otherwise do whatever you wanted to do in your second overload:

template <class T> void f_imp(T&, std::true_type) {std::cout << "lvalue\n";}
template <class T> void f_imp(T&&, std::false_type) {std::cout << "rvalue\n";}

template <typename T> void f(T&& x)
{
     f_imp(std::forward<T>(x), std::is_lvalue_reference<T>());
}

And use std::forward to perfectly forward x to the implementation-detail function.

like image 134
Howard Hinnant Avatar answered Jan 25 '23 23:01

Howard Hinnant