Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding template argument deduction with rvalue/lvalue

This is a followup from function template does not recognize lvalue

Lets play with the following code:

#include <iostream>
template <class T>
void func(T&&) {
  std::cout<<"in rvalue\n";
}

template <class T>
void func(const T&) {
  std::cout<<"in lvalue\n";
}

int main()
{
    double n=3;
    func<double>(n);
    func(n);
}

It prints:

in lvalue
in rvalue

I don't understand what's happening in the second call. How the compiler resolve the template parameter ? Why isn't there any ambiguity ?

like image 853
hivert Avatar asked Mar 04 '14 09:03

hivert


People also ask

What is the difference between an rvalue and an lvalue reference?

Now an lvalue reference is a reference that binds to an lvalue. lvalue references are marked with one ampersand (&). And an rvalue reference is a reference that binds to an rvalue. rvalue references are marked with two ampersands (&&).

What is a template argument deduction?

Template argument deduction is also performed when the name of a class template is used as the type of an object being constructed: Template argument deduction for class templates takes place in declarations and in explicit cast expressions; see class template argument deduction for details.

Can a template parameter be deduced from a default argument?

Type template parameter cannot be deduced from the type of a function default argument: Deduction of template template parameter can use the type used in the template specialization used in the function call: Besides function calls and operator expressions, template argument deduction is used in the following situations:

Can lvalue const references bind to rvalues?

I mentioned that lvalue const references could bind to rvalues: void f (MyClass const& x) { ... } but they are const, so even though they can bind to a temporary unnamed object that no one cares about, f can’t modify it.


1 Answers

When you say func<double>(n), there's no argument deduction, since you specify the argument, and so the choice is between func(double &&) and func(const double &). The former isn't viable, because an rvalue reference cannot bind to an lvalue (namely n).

Only func(n) performs argument deduction. This is a complex topic, but in a nutshell, you have these two possible candidates:

T = double &:    func(T &&)       -->   func(double &)          (first overload)
T = double:      func(const T &)  -->   func(const double &)    (second overload)

The first overload is strictly better, because it requires one less conversion of the argument value (namely from double to const double).

The magic ingredient is the "reference collapsing", which means that T && can be an lvalue reference when T is itself a reference type (specificaly, double & && becomes double &, and that allows the first deduction to exist).

like image 171
Kerrek SB Avatar answered Oct 08 '22 16:10

Kerrek SB