1

Before C++11, the template type deduction is quite simple:

template <typename X>
void bar(X i) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Type X will be whatever type of the parameter the caller passes in.

Now, for C++11, I read an article about rvalue reference from Scott Meyers.

It states

template<typename T>
void f(T&& param);

During type deduction for a template parameter that is a universal reference, lvalues and rvalues of the same type are deduced to have slightly different types. In particular, lvalues of type T are deduced to be of type T& (i.e., lvalue reference to T), while rvalues of type T are deduced to be simply of type T.

I wonder why lvalues of type T are deduced to be of type T& while rvalues of type T are deduced to be simply of type T.

Is it just something to memorize? Or?

Hei
  • 1,844
  • 3
  • 21
  • 35
  • Note that the example you presented becomes incorrect when `X` is a reference type, e.g. `double&`. In that case, `X` would still be deduced as `double`, but the parameter type would be `double&`, and so they differ. There is no single type `X`, but two types, the deduced type and the parameter type. – davidhigh Oct 31 '14 at 13:18

2 Answers2

4

The only question that is to answer is why T& leads to T& and not T (as it would in "normal" type deduction). For that, I guess the answer is "perfect forwarding".`


EDIT: More detailed, consider a case where you perfectly forward a class constructor:

struct Widget    //... to stay in Scott Meyers terminology
{
     double &x;
     Widget(double &_x) : x(_x) {}

};

class Manager : public Widget
{
     template<typename ... Args>
     Manager(Args&& ... args) : Widget(std::forward<Args>(args) ...) {}
};

If you invoke Manager by

double d=1.0;
Manager(d);

the type of Args&&... gets deduced to double &, according to the rules you mentioned. By this, the Manager class constructor behaves as

Manager(double &d) : Widget(std::forward<double&>(d)) {}

The effect of std::forward<double &>(d) is then basically a static_cast<double&&>((double &) d), which by the rules of reference collapsing remains (double &) (d). As a result, the constructor becomes

Manager(double &d) : Widget((double &) d) {}

With this, the variable is correctly passed to the class Widget, and it is also ensured that the correct constructor -- the one which takes a reference -- is called.


In contrast, if the type weren't deduced as double &, but rather as double, the Manager class constructor would behave like

Manager(double &d) : Base(std::forward<double>(d)) {}

which becomes translated into

Manager(double &d) : Base((double &&) (d)) {}

That is, the lvalue reference is casted to an rvalue reference (-- as if std::move had been applied).

With this, it is probably still possible to take the reference, as the cast didn't change the address of the variable. However, now you can not be sure that the correct constructor in Widget is called -- there could also be another one taking an rvalue reference, which would then incorrectly be called.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • Thanks for your reply. I am trying to understand why passing in a lvalue means T to be deduced to be of type T& while passing in a rvalue means T to be deduced to be of type T. Is it just a rule to memorize? – Hei Oct 31 '14 at 11:52
  • I added more info. Hope that readers get my point...probably just a dumb question that it is just a rule to memorize. – Hei Oct 31 '14 at 11:57
  • I wrote some more details and also corrected the rationale of the example. – davidhigh Oct 31 '14 at 12:58
  • thanks for your post. I have read couple article about rvalue reference and related. Your post helps me understand couple missing pieces -- when the collapsing rules kick in. I didn't know they kick in when std::forward() is called. thanks! – Hei Nov 03 '14 at 11:11
0

I think the reason is that both T and T& denote lvalues when appear as a parameter type; It's just that T denote lvalue as local variable, whereas T& means "alias to an lvalue from somewhere else".

It is not likely that a generic function will work well when instantiated for both T, T&, and T&&, since the semantics for the latter are quite different from those of lvalues. So, of you want an rvalue, you should state it explicitly.

Elazar
  • 20,415
  • 4
  • 46
  • 67