29

This is an rvalue reference:

void foo(int&& a);

It does not bind to lvalues:

int i = 42;
foo(i);   // error

This is a universal reference:

template<typename T>
void bar(T&& b);

It binds to rvalues and it also binds to lvalues:

bar(i);   // okay

This is an rvalue reference:

template<typename T>
struct X
{
    void baz(T&& c);
};

It does not bind to lvalues:

X<int> x;
x.baz(i);   // error

Why do universal references use the same syntax as rvalue references? Isn't that an unnecessary source of confusion? Did the committee ever consider alternative syntaxes like T&&&, T&*, T@ or T&42 (just kidding on that last one)? If so, what were the reasons for rejecting alternative syntaxes?

M.M
  • 138,810
  • 21
  • 208
  • 365
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • Thanks, I was just getting things straight. – chris Jan 13 '13 at 11:12
  • 7
    "Universal references" is not a term in the standard. They're all r-value refs and ref collapsing rules kick in sometimes. So there's no syntax because "there's no such thing as universal references" in the standard. – Mat Jan 13 '13 at 11:14
  • "It does not bind to lvalues [...] It also binds to lvalues"? – Luchian Grigore Jan 13 '13 at 11:21
  • @LuchianGrigore, It binds to rvalues and lvalues. I presume that's what was meant by "also". – chris Jan 13 '13 at 11:22
  • @LuchianGrigore rvalue references do *not* bind to lvalues, but universal references *do*. – fredoverflow Jan 13 '13 at 11:25
  • 2
    @Mat Right, but the reference collapsing rules belong to the design I am questioning :) It's not like the reference collapsing rules were god-given, and rvalue references had to be designed around them :) – fredoverflow Jan 13 '13 at 11:27
  • 6
    Useful link [T&& Doesn’t Always Mean “Rvalue Reference”](http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers) _from Scott Meyers_ – masoud Jan 13 '13 at 11:41
  • @FredOverflow: from what I remember from watching that talk, the question (why no new syntax) was asked and the only rationale given by Scott Meyers was "because [we] didn't want to add a new token to the language"... and I'm not sure he was joking :) – Mat Jan 13 '13 at 11:44
  • @Mat Well, that would have ruled out `T@`, but neither `T&&&` nor `T&*` would have introduced any new tokens. They would have been parsed as "lvalue reference to rvalue reference to T" and "pointer to lvalue reference", neither of which was well-formed before, so there would have been no ambiguity. – fredoverflow Jan 13 '13 at 11:48
  • Universal references are more of an emergent property of the language than a core element of the design... – Kerrek SB Jan 13 '13 at 11:52
  • 2
    @KerrekSB, not really, the rules had to be changed to allow `T&&` to deduce `T` as a reference type, it didn't just emerge. – Jonathan Wakely Jan 13 '13 at 13:43
  • 2
    `X x;` misses the template arguments. The code should compile when using `X x;`. – Konrad Rudolph Jan 13 '13 at 14:18
  • @JonathanWakely: Yeah, that's true actually. Oh well. – Kerrek SB Jan 13 '13 at 19:19

3 Answers3

23

A universal reference such as T&& can deduce T to be an "object type", or a "reference type"

In your example it can deduce T as int when passed an rvalue, so the function parameter is int&&, or it can deduce T as int& when passed an lvalue, in which case the function parameter is int& (because the reference collapsing rules say std::add_rvalue_reference<int&>::type is just int&)

If T isn't deduced by the function call (as in your X::baz example) then it can't be deduced to int&, so the reference isn't a universal reference.

So IMHO there's really no need for new syntax, it fits nicely into template argument deduction and reference collapsing rules, with the small tweak that a template parameter can be deduced as a reference type (where in C++03 a function template parameter of type T or T& would always deduce T as an object type.)

These semantics and this syntax were proposed right from the beginning when rvalue references and a tweak to the argument deduction rules were proposed as the solution to the forwarding problem, see N1385. Using this syntax to provide perfect forwarding was proposed in parallel with proposing rvalue references for the purposes of move semantics: N1377 was in the same mailing as N1385. I don't think an alternative syntax was ever seriously proposed.

IMHO an alternative syntax would actually be more confusing anyway. If you had template<typename T> void bar(T&@) as the syntax for a universal reference, but the same semantics as we have today, then when calling bar(i) the template parameter T could be deduced as int& or int and the function parameter would be of type int& or int&& ... neither of which is "T&@" (whatever that type is.) So you'd have grammar in the language for a declarator T&@ which is not a type that can ever exist, because it actually always refers to some other type, either int& or int&&.

At least with the syntax we've got the type T&& is a real type, and the reference collapsing rules are not specific to function templates using universal references, they're completely consistent with the rest of the type system outside of templates:

struct A {} a;
typedef A& T;
T&& ref = a;    // T&& == A&

Or equivalently:

struct A {} a;
typedef A& T;
std::add_rvalue_reference<T>::type ref = a;    // type == A&

When T is an lvalue reference type, T&& is too. I don't think a new syntax is needed, the rules really aren't that complicated or confusing.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • 6
    +1 Nicely explained. Scott contacted me prior to his using his invented term *universal reference* and I expressed concern that this may actually cause more confusion than teaching what the standard actually says. Nevertheless Scott has an outstanding track record of being an excellent teacher of C++. Maybe use of his term will help people learn. Or maybe it will just cause confusion. I honestly don't know. In any event, Jonathan's explanation of why things are the way they are, and references to papers is spot on. – Howard Hinnant Jan 13 '13 at 17:09
  • 1
    @HowardHinnant, same here, I had been planning to write a piece for the ACCU journal _Overload_ explaining it in terms of the `typedef` and `add_rvalue_reference` examples above, but Scott published first. I had exactly the same reservations when reviewing his piece for _Overload_ but decided that because he's a far better teacher than I am (with a far wider readership!) I'd trust him to do a better job of explaining it to the world. I now use the term universal references in public, but I still think in terms of deduction and ref-collapsing. – Jonathan Wakely Jan 13 '13 at 17:17
  • 5
    Hmm, just noting that Daveed answered to my mild [rage on cpp-next.com](http://cpp-next.com/archive/2009/12/onward-forward/comment-page-1/#comment-270) by saying *"I (and others) voiced my preference for not trying to fit everything under the rvalue reference type, and instead introduce forwarding-specific notation"* and *"There were from the start people (e.g., colleagues and I) who thought the merging of “moving” and “forwarding” was a terrible idea, and we sketched out “other ways”* but also *"My bad for not putting in the energy to push those sketches forward as a competing proposal."* – Johannes Schaub - litb Jan 13 '13 at 21:12
  • Thanks, @JohannesSchaub-litb -- as I said, nothing was ever seriously proposed. If there's a proposal on the table to do it one way and noone offers an alternative proposal, then generally the one on the table wins. – Jonathan Wakely Jan 14 '13 at 00:19
  • 1
    @Johannes `template void ff(for T) { … } // T deduced to & or &&` What a beautiful repurposing of the `for` keyword :) – fredoverflow Jan 14 '13 at 15:46
7

Why do universal references use the same syntax as rvalue references? Isn't that an unnecessary source of confusion? Did the committee ever consider alternative syntaxes...

Yes, it is confusing, IMO (I'll disagree with @JonathanWakely here). I remember that during an informal discussion (lunch, I think) about the early design of the overall feature we did discuss different notations (Howard Hinnant and Dave Abrahams were there bringing their idea and the EDG guys were giving feedback on how it could be fit in the core language; this predates N1377). I think I remember &? and &|&& were considered, but all this was verbal; I'm not aware of meeting notes having been taken (but I believe this is also when John suggested the use of && for rvalue references). Those were the early stages of the design, however, and there were plenty of fundamental semantic issues to consider at the time. (E.g., during that same lunch discussion we also raised the possibility of not having two kinds of references, but instead having two kinds of reference parameters.)

A more recent aspect of the confusion this causes is found in the C++17 feature of "class template argument deduction" (P0099R3). There a function template signature is formed by transforming the signature of constructors and constructor templates. For something like:

template<typename T> struct S {
  S(T&&);
};

a function template signature

template<typename T> auto S(T&&)->S<T>;

is formed to use for the deduction of a declaration like

int i = 42;
S s = i;  // Deduce S<int> or S<int&>?

Deducing T = int& here would be counter-intuitive. So we're having to add a "special deduction rule to disable the special deduction rule" in this circumstance :-(

T.C.
  • 133,968
  • 17
  • 288
  • 421
Daveed V.
  • 946
  • 8
  • 8
2

As a developer with only a few years of C++ experience, I must agree that universal references are confusing. It seems to me that this stuff is completely impossible to understand, let alone actively use without reading Scott Meyers and/or watching the relevant talks on YouTube.

It is not just that && can be either an r-value reference or a "universal reference", it is the way templates are used to distinguish the types of reference. Imagine you are a normal engineer working in software development. You read something like:

template <typename T>
void foo (T&& whatever) {...}

and then the function body of foo tells you that the input parameter must be a very specific class type that has been defined in an in-house library. Wonderful, you think, the template is obsolete in the current software version, probably its a leftover from previous developments, so let's get rid of it.

Good luck with the compiler errors…

Nobody who hasn't learned this explicitly would assume that you'd implement foo as a function template here just to employ template deduction rules and reference collapsing rules which happen to coincide so that you end up with what is called "perfect forwarding". It really looks more like a dirty hack than anything else. I think creating a syntax for universal references would have made things simpler, and a developer who used to work on an older code base would at least know that there is something that has to be googled here. Just my two cents.

Andy
  • 21
  • 1