4
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.

Cosyn
  • 4,404
  • 1
  • 33
  • 26

2 Answers2

5

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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • while it is indeed correct to point out that one should not write such an overload, from memory i remember that the first template is intended to be more specialized (that wasnt always the case in c++0x drafts), and the wording appears to reflect that intent. – Johannes Schaub - litb Mar 27 '12 at 08:08
  • @JohannesSchaub-litb: Thanks, I've corrected my answer per your comment. – Howard Hinnant Mar 27 '12 at 13:09
4

Your interpretation of the standard appears to be correct.

template <typename T> void f(T&) {}  // #1
template <typename T> void f(T&&) {} // #2

In #1, T is successfully deduced as int, and in #2, T is successfully deduced as int&, so partial ordering is performed to select the function to call. During partial ordering for the call f(x), the types of the first (only, in this case) argument will be ordered ([temp.deduct.partial]/3 bullet 1). During deduction in both directions, type P will be T, and type A will be a synthesized type representing T ([temp.deduct.partial]/5), so deduction succeeds in both directions.

As you observed, [temp.deduct.partial]/9 then applies, and says that #1's first argument is more specialized. Therefore, by [temp.deduct.partial]/10, #1 is selected as the most-specialized template and its specialization is the result of overload resolution.

You didn't mention which compiler you are using. I assume it's g++ -- this appears to be a bug in that compiler (I've tested versions between 4.4.3 and 4.7, and they all reject this code). clang accepts your code, and calls the f(T &) overload as you expected.

Richard Smith
  • 13,696
  • 56
  • 78