3

Yesterday I asked a question about when to use std::forward and when to use std::move

Today I was trying to apply what I think I learned. I wrote the following:

template <typename T>
void exp(T a, T b)
{
  cout << "rvalues" << endl;
}

template <typename T>
void exp(T& a, T& b)
{
  cout << "lvalues" << endl;
}

template <typename T>
void foo(T&& a, T&& b)
{
  exp(forward<T>(a), forward<T>(b));
}

When in the main I call foo(4, 5) it prints out "rvalue", as I would expect, but when I do something like

int a = 0, b = 0;
foo(a, b);

an error occurs that says: 'exp' : ambiguous call to overloaded function

What am I missing here? Why the last call to foo(a, b) doesn't call the void exp(T& a, T& b) function?

Community
  • 1
  • 1
Astinog
  • 1,151
  • 3
  • 12
  • 35

2 Answers2

3

Both reference binding and an lvalue-to-rvalue conversion are given an exact match rank:

§ 13.3.3.1.4 [over.ics.ref]/p1:

When a parameter of reference type binds directly (8.5.3) to an argument expression, the implicit conversion sequence is the identity conversion.

Thus, the compiler can't choose between the two on the basis of better conversion sequence selection, and tries to partially order the two overloads of exp (because the more specialized function templates take precedence in overload resolution). However:

§ 14.8.2.4 [temp.deduct.partial]/p5:

Before the partial ordering is done, certain transformations are performed on the types used for partial ordering:

— If P is a reference type, P is replaced by the type referred to.

— If A is a reference type, A is replaced by the type referred to.

This makes the two overloads indistinguishable, since neither is more specialized, as from the partial ordering point of view they look the same, and no other exception applies.

If your primary goal is to have one overload for rvalues and another for lvalues, you can define them as follows:

template <typename T>
void exp(T&& a, T&& b) {}

template <typename T>
void exp(T& a, T& b) {}

Now, although exp(T&& a, T&& b) is viable for lvalues as well, the other overload is deemed more specialized:

§ 14.8.2.4 [temp.deduct.partial]/p9:

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; otherwise [...]

which makes exp(T& a, T& b) to be the preffered one for lvalues, and exp(T&& a, T&& b) the only viable for rvalues in turn.

DEMO

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
0

Your version of exp which prints rvalues is incorrect. It should be:

template <typename T>
void exp(T&& a, T&& b)
{
  cout << "rvalues" << endl;
}

If you call foo(a, b) the compiler could have chosen to copy a and b and call your rvalues version when in fact they are not rvalues. That is why you received the compilation error.

If you really want to see if the argument is an rvalue you can use is_rvalue_reference:

template <typename T>
void exp(T&& a, T&& b)
{
  std::cout << "rvalues: " << std::is_rvalue_reference<T&&>::value << std::endl;
}

If you want one function for rvalues and one for lvalues then you can use enable_if:

template <typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type exp(T&& a, T&& b)
{
  std::cout << "rvalues" << std::endl;
}

template <typename T>
typename std::enable_if<!std::is_rvalue_reference<T&>::value, void>::type exp(T& a, T& b)
{
  std::cout << "lvalues" << std::endl;
}

You might need to include the type_traits header file.

Jonathan
  • 552
  • 1
  • 4
  • 10