37

I just made some research about those (quite) new features and I wonder why C++ Committee decided to introduce the same syntax for both of them? It seems that developers unnecessary have to waste some time to understand how it works, and one solution lets to think about further problems. In my case it started from problem which can be simplified to this:

#include <iostream>

template <typename T>
void f(T& a)
{
    std::cout << "f(T& a) for lvalues\n";
}

template <typename T>
void f(T&& a)
{
    std::cout << "f(T&& a) for rvalues\n";
}

int main()
{
    int a;
    f(a);
    f(int());

    return 0;
}

I compiled it firstly on VS2013 and it worked as I expected, with this results:

f(T& a) for lvalues
f(T&& a) for rvalues

But there was one suspicious thing: intellisense underlined f(a). I made some research and I understood that it is because type collapsing (universal references as Scott Meyers named it), so I wondered what g++ thinks about it. Of course it didn't compiled. It is very nice that Microsoft implemented their compiler to work in more intuitive way, but I'm not sure if it is according to the standard and if there should be this kind of difference in IDE (compiler vs intellisense, but in fact there may be some sense in it). Ok, return to the problem. I solved it in this way:

template <typename T>
void f(T& a)
{
    std::cout << "f(T& a) for lvalues\n";
}

template <typename T>
void f(const T&& a)
{
    std::cout << "f(T&& a) for rvalues\n";
}

Now there wasn't any type collapsing, just normal overloading for (r/l)values. It compiled on g++, intellisense stopped complaining and I was almost satisfied. Almost, because I thought about what if I will want to change something in object's state which is passed by rvalue reference? I could describe some situation when it could be necessary, but this description is too long to present it here. I solved it in this way:

template <typename T>
void f(T&& a, std::true_type)
{
    std::cout << "f(T&& a) for rvalues\n";
}

template <typename T>
void f(T&& a, std::false_type)
{
    std::cout << "f(T&& a) for lvalues\n";
}

template <typename T>
void f(T&& a)
{
    f(std::forward<T>(a), std::is_rvalue_reference<T&&>());
}

Now it compiles on all tested compilers and it lets me to change object state in rvalue reference implementation, but it doesn't looks very nice, and this is because of the same syntax for universal references and rvalue references. So my question is: Why C++ Committee didn't introduce some another syntax for universal references? I think that this feature should be signalized, for example, by T?, auto?, or something similar, but not as T&& and auto&& which just collide with rvalue references. Using this approach my first implementation would be perfectly correct, not only for MS compiler. Can anyone explain Committee decision?

Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
Piotrek
  • 451
  • 1
  • 5
  • 9
  • because rvalue references and universal references are strongly related, also with `T &` reference collapsing occurs but you can't have a temporary (`&&`) where an lval is required – aaronman Dec 04 '13 at 00:11
  • It seems that there's a cryptic rhetorical question in here. You're abusing the language, but it's not clear why. – Potatoswatter Dec 04 '13 at 00:11
  • 5
    Universal references aren't a "real" thing in the sense that it is a specified language term. So the premise that the committee gave them a syntax at all isn't meaningful, because the committee has no notion of the thing in the language. A universal reference refers to the *behavior* around rvalue references and template deduction, and refers to it informally. The reason why the syntax is the same is because they are the same thing: rvalue references. Universal references just refer to a subset of rvalue reference behavior. – GManNickG Dec 04 '13 at 00:14
  • @GManNickG it's mostly the behavior introduced by reference collapsing – aaronman Dec 04 '13 at 00:14
  • 2
    @Piotrek shouldn't it say "f(T& a) for rvalues\n" in the first function? – Vladislavs Burakovs Jan 30 '15 at 07:36
  • 1
    The answers so far don't really answer the question (except Cassio Neri's). "universal reference" isn't just a subset of normal reference behaviour. The difference is that **only if** the parameter is `T&&`, then `T` may be deduced to a reference type. This is new behaviour compared to the case of parameter `T` or `T&` for example, in which case `T` is never deduced to a reference type. (Reference collapsing occurs after type deduction). OP is asking about rationale for why the same old syntax suddenly has a new case, instead of introducing new syntax. – M.M May 03 '16 at 03:55
  • 1
    During development of C++11 some people felt new syntax should be used, as it is confusing that `T` can deduce to a reference type depending n how `T` is used in the parameter list. But they lost the vote. – M.M May 03 '16 at 03:56

5 Answers5

45

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.

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
Laurent LA RIZZA
  • 2,905
  • 1
  • 23
  • 41
  • Lauren, the collapsing rules are clear. I do not see why type deduction matters. Well, the type is specified, either directly or indirectly. Why the collapsing rules should be different? You maybe better modify your answer. – Kirill Kobelev Jul 11 '16 at 12:32
  • 1
    @KirillKobelev: Thank you for the edit. It looks like my grammar needed it. I don't get what you're suggesting in your comment. What are you saying the collapsing rules are different of? I mentioned 3 sets of collapsing rules, in order : the untold collapsing rules of C++98, the collapsing rules of C++11 and the subset of the C++ collapsing rules which have `&&` on the right. – Laurent LA RIZZA Jul 12 '16 at 04:45
7

Others have already mentioned that reference collapsing rules are key for universal references to work but there's another (arguably) equally important aspect: template argument deduction when the template parameter is of the form T&&.

Actually, in relation to the question:

Why “universal references” have the same syntax as rvalue references?

in my opinion the form of the template parameter is more important because this is all about syntax. In C++03 there was no way to a template function to know the value category (rvalue or lvalue) of the passed object. C++11 changed the template argument deduction to account for that: 14.8.2.1 [temp.deduct.call]/p3

[...] If P is an rvalue reference to a cv-unqualified template parameter and the argument is an lvalue, the type "lvalue reference to A" is used in place of A for type deduction.

This a bit more complicated than the originally proposed wording (given by n1770):

If P is an rvalue-reference type of the form cv T&& where T is a template type-parameter, and the argument is an lvalue, the deduced template argument value for T is A&. [Example:

template<typename T> int f(T&&);
int i;
int j = f(i);   // calls f<int&>(i)

--- end example]

In more detail, the call above triggers the instantiation of f<int&>(int& &&) which, after reference collapsing is applied, becomes f<int&>(int&). On the other hand f(0) instantiates f<int>(int&&). (Notice there's no & inside < ... >.)

No other form of declaration would deduce T to int& and trigger the instantiation of f<int&>( ... ). (Notice that a & can appear between ( ... ) but not between < ... >.)

In summary, when type deduction is performed the syntactic form T&& is what allows the value category of the original object to be available inside the function template body.

Related to this fact, notice that one must use std::forward<T>(arg) and not std::forward(arg) exactly because it's T (not arg) that records the value category of the original object. (As a mesaure of caution, the definition of std::forward "artificially" forces the latter compilation to fail to prevent programmers making this mistake.)

Back to the original question: "Why the committee decided to use the form T&& rather than choose a new syntax?"

I can't tell the real reason but I can speculate. First this is backward compatible with C++03. Second and, most importantly, this was a very simple solution to state in the Standard (one paragraph change) and to implement by compilers. Please, don't get me wrong. I'm not saying the committee members are lazy (they certainly aren't). I'm just saying that they minimized the risk of collateral damage.

Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
6

You answered your own question: "universal reference" is just a name for the rvalue reference case of reference collapsing. If another syntax were required for reference collapsing, it wouldn't be reference collapsing any more. Reference collapsing is simply applying a reference qualifier to a reference type.

so I wondered what g++ thinks about it. Of course it didn't compiled.

Your first example is well-formed. GCC 4.9 compiles it without complaint, and the output agrees with MSVC.

Almost, because I thought about what if I will want to change something in object's state which is passing by rvalue reference?

Rvalue references do not apply const semantics; you can always change the state of an object passed by move. Mutability is necessary to their purpose. Although there is such a thing as const &&, you should never need it.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • If by *"your second example"*, you're referring to the one with `void f(const T&& a)`, then I'm not sure where the reference collapsing occurs. Type deduction uses the types w/o references, so for the first overload, `T` is deduced to a non-reference type; the second overload `f(const T&&)` does not contain a universal reference (because of the c-qualifier), so `T` here isn't deduced to a reference type either. – dyp Dec 04 '13 at 10:23
  • @DyP You're right; the `const` precludes `T` from being a reference. Such an overload can only match an actual `const &&` reference, which is difficult to obtain without applying `move` to a constant lvalue. (`const` is automatically dropped from the type of any prvalue.) – Potatoswatter Dec 04 '13 at 12:31
  • In my case (I've tested it on GCC 4.7.3 and 4.8.1), compiler gives this compilation error: test.cpp: In function 'int main()': test.cpp:18:8: error: call of overloaded 'f(int&)' is ambiguous test.cpp:18:8: note: candidates are: test.cpp:4:6: note: void f(T&) [with T = int] test.cpp:10:6: note: void f(T&&) [with T = int&] I know that const semantic turns off universal references, and that is exactly what I want to achieve. But I would like to be able to turns it off also in case of non const template and that is the point. – Piotrek Dec 04 '13 at 21:19
  • Downvoted because the first example does not compile with gcc 4.8. The first version of g++ to compile this correctly was gcc 4.9. The gcc-5 and gcc-6 series also compile this correctly. – David Hammen May 02 '16 at 12:02
  • @DavidHammen Fixed. I must have been using a trunk build and the version number hadn't been bumped. – Potatoswatter May 03 '16 at 03:01
1

First and foremost, the reason the first example didn't compile with gcc 4.8 is that this is a bug in gcc 4.8. (I'll expand on this later). The first example compiles, runs, and produces the same output as VS2013 with post-4.8 versions of gcc, clang 3.3 and later, and Apple's LLVM-based c++ compiler.

On universal references:
One reason Scott Meyer's coined the term "universal references" is because T&& as a function template argument matches lvalues as well as rvalues. The universality of T&& in templates can be seen by removing the first function from the first example in the question:

// Example 1, sans f(T&):
#include <iostream>
#include <type_traits>

template <typename T>
void f(T&&)   {
    std::cout << "f(T&&) for universal references\n";
    std::cout << "T&& is an rvalue reference: "
              << std::boolalpha
              << std::is_rvalue_reference<T&&>::value
              << '\n';
}

int main() {
    int a;
    const int b = 42;
    f(a);
    f(b);
    f(0);
}

The above compiles and runs on all the aforementioned compilers, and on gcc 4.8 as well. This one function universally accepts lvalues and rvalues as arguments. In the case of the calls to f(a) and f(b), the function reports that T&& is not an rvalue reference. The calls to f(a), f(b) and f(0) respectively become calls to the functions f<int&>(int&), f<const int&>(const int&), f<int&&>(int&&). Only in the case of f(0) is does the T&& become an rvalue reference. Since an argument T&& foo may or may not be an rvalue reference in the case of a function template, it's best to call those something else. Meyers chose to call them "universal references."

Why this is a bug in gcc 4.8:
In the first example code in the question, the function templates template <typename T> void f(T&) and template <typename T> void f(T&&) become f<int>(int&) and f<int&>(int&) with regard to the call to f(a), the latter thanks to C++11 reference collapsing rules. These two functions have the exact same signature, so perhaps gcc 4.8 is correct, that the call to f(a) is ambiguous. It is not.

The reason the call is not ambiguous is that template <typename T> void f(T&) is more specialized than is template <typename T> void f(T&&) per the rules of sections 13.3 (Overload resolution), 14.5.6.2 (Partial ordering of function templates), and 14.8.2.4 (Deducing template arguments during partial ordering). When comparing template <typename T> void f(T&&) to template <typename T> void f(T&) with T=int&, both product f(int&). No distinction can be made here. However, when comparing template<typename T> void f(T&) to template <typename T> void f(T&&) with T=int, the former is more specialized because now we have f(int&) versus f(int&&). Per 14.8.2.4 paragraph 9, "if the type from the argument template was an lvalue reference and the type from the parameter template was not, the argument type is considered to be more specialized than the other".

David Hammen
  • 32,454
  • 9
  • 60
  • 108
0

Universal references work because of the reference collapsing rules in c++11. if you have

template <typename T> func (T & t)

Reference collapsing still occurs but it won't work with a temporary hence the reference is not "universal". A universal reference is called "universal" because it can accept lvals and rvals (also preserves other qualifiers). T & t is not universal as it cannot accept rvals.

So to sum it up universal references are a product of reference collapsing, and a universal reference is named as such because it is universal it what it can be

aaronman
  • 18,343
  • 7
  • 63
  • 78