Why do forwarding references have the same syntax as rvalue references?

I think it happened the other way around. The initial idea was to introduce rvalue-references into the language, meaning that “the code providing the double-ampersand reference does not care about what will happen to the referred-to object”. This permits move semantics. This is nice.

Now. The standard forbids constructing a reference to a reference, but this was always possible. Consider:

template<typename T>
void my_func(T, T&) { /* ... */ }

// ...

my_func<int&>(a, b);

In this case the type of the second parameter should be int & &, but this is explicitly forbidden in the standard. So the references have to be collapsed, even in C++98. In C++98, there was only one kind of reference, so the collapsing rule was simple:

& & -> &

Now, we have two kinds of references, where && means “I don’t care about what may happen to the object”, and & meaning “I may care about what may happen to the object, so you better watch what you’re doing”. With this in mind, the collapsing rules flow naturally: C++ should collapse referecnces to && only if no one cares about what happens to the object:

& & -> &
& && -> &
&& & -> &
&& && -> &&

With these rules in place, I think it’s Scott Meyers who noticed that this subset of rules:

& && -> &
&& && -> &&

Shows that && is right-neutral with regards to reference collapsing, and, when type deduction occurs, the T&& construct can be used to match any type of reference, and coined the term “Universal reference” for these references. It is not something that has been invented by the Committee. It is only a side-effect of other rules, not a Committee design.

And the term has therefore been introduced to distinguish between REAL rvalue-references, when no type deduction occurs, which are guaranteed to be &&, and those type-deduced UNIVERSAL references, which are not guaranteed to remain && at template specialization time.

Leave a Comment