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.