0

Stroustrup C++ 4th Ed Page 196 has a section called References to References. I have included the code. Does anyone have examples of situations where these references to references would be used?

I'm having a challenging time conceptualizing this. Thanks

using rr_i = int&&;
using lr_i = int&;
using rr_rr_i = rr_i&&; // ‘‘int && &&’’ is an int&&
using lr_rr_i = rr_i&; // ‘‘int && &’’ is an int&
using rr_lr_i = lr_i&&; // ‘‘int & &&’’ is an int&
using lr_lr_i = lr_i&; // ‘‘int & &’’ is an int&

In other words, lvalue reference always wins.

notaorb
  • 1,944
  • 1
  • 8
  • 18
  • 1
    The only case in which a 'reference to reference' can arise is, notionally, during template argument deduction, in which case it gets reference-collapsed to 'just a reference'. The link explains that. This is extremely useful/important for template deduction, perfect forwarding, etc. – underscore_d Jun 07 '20 at 16:12
  • @und it can also happen when doing `auto&& bar = foo();`. No? – Aykhan Hagverdili Jun 07 '20 at 16:13
  • @_Static_assert Sure, but isn't that following the same rules as template argument deduction? Sorry for not being clear; I think of those as one and the same thing, but I appreciate that's not obvious to people who don't already conceptualise it that way. – underscore_d Jun 07 '20 at 16:14
  • @underscore_d regarding that article, it seems one could write an entire book on this topic. Still reading.... – notaorb Jun 07 '20 at 16:38

2 Answers2

1

They wouldn't be.

The comments show that the "extra" references are dropped. That's a good thing, because otherwise the program would be invalid (as there are no "references to references" in the language).

[dcl.ref]/6: If a typedef-name ([dcl.typedef], [temp.param]) or a decltype-specifier denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR. [ Note: This rule is known as reference collapsing. — end note ] [ Example:

int i;
typedef int& LRI;
typedef int&& RRI;

LRI& r1 = i;                    // r1 has the type int&
const LRI& r2 = i;              // r2 has the type int&
const LRI&& r3 = i;             // r3 has the type int&

RRI& r4 = i;                    // r4 has the type int&
RRI&& r5 = 5;                   // r5 has the type int&&

decltype(r2)& r6 = i;           // r6 has the type int&
decltype(r2)&& r7 = i;          // r7 has the type int&

— end example ]

This is a tool to make template programming easier: you don't have to strip the "redundant" references off yourself. Arguably you wouldn't often encounter the need for this, but in more complex codebases with lots of templates and/or aliases, it can happen.


When you have a function template like this:

template <typename T>
void foo(T&& arg)

and pass an lvalue int, then your arg will be an int&; if you pass an rvalue int, then it'll be an int&&. This is called a "forwarding reference" (or "universal reference"), and is not the same rule as reference collapsing, though it does have similar utility in making life easier for template programming.

Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35
0

This is a reference

int i;
int& ii = i;

A reference can't be referenced. This is NOT a reference to reference. Both ii and iii are references to i:

int i;
int& ii = i;
int& iii = ii;

This is an rvalue reference

int&& r = 123;

This is a nonsense and will fail compilation.

int i;
int& ii = i;
int&& nonsense = &ii;//fail compilation
int&& nonsense2  = i;//fail compilation

But a pointer can be pointed and referenced

int i, j;
int* p = &i;
int** pp = &p;
int*& rp = p;
rp = &j;
armagedescu
  • 1,758
  • 2
  • 20
  • 31
  • `r` is still an lvalue in that example. – Asteroids With Wings Jun 07 '20 at 16:46
  • The reason `int&& nonsence = &ii` fails compilation is that you gave it a pointer – Asteroids With Wings Jun 07 '20 at 16:47
  • and none of this explains reference collapsing – Asteroids With Wings Jun 07 '20 at 16:47
  • @AsteroidsWithWings how is r and lvalue? Is that not a literal and if the literal cannot be an lvalue, it is an rvalue. – notaorb Jun 07 '20 at 16:54
  • @AsteroidsWithWings agree, this doesn't not explain ref collapsing. – notaorb Jun 07 '20 at 16:54
  • @notaorb `123` is an rvalue. `r` is not. It's the name of an `int` object that was created from the rvalue expression (and literal) `123` and lifetime-extended by being bound to a reference. – Asteroids With Wings Jun 07 '20 at 16:54
  • Also note that value categories relate to expressions, not to things/objects. It does not make sense to say that any particular object "is" an rvalue or an lvalue. Only expressions can be called that. An example of an expression is `r`, which in this case contains the name of an object and nothing else. But the object itself that is being named has no value category, only a type. – Asteroids With Wings Jun 07 '20 at 16:57
  • @AsteroidsWithWings regarding r and 123. Is r not an "rvalue" reference. That is the information I have learned so far in Stroustrup up to this page. Maybe I'm missing something – notaorb Jun 07 '20 at 17:03
  • @notaorb rvalues and rvalue references are not the same thing. Yes it's confusing. There's a lot of complicated machinery at work here. Your declaration introduces an rvalue reference called `r` that refers to a temporary `int` constructed from the rvalue expression (and literal) `123`. An expression consisting of the symbols `r` is an lvalue expression of type `int` that refers to that `int`. You'd have to write `std::move(r)` to get an rvalue expression referring to it again; indeed, this is why `std::move` exists! – Asteroids With Wings Jun 07 '20 at 17:04
  • So is r and rvalue reference and 123 an rvalue? – notaorb Jun 07 '20 at 17:05
  • @AsteroidsWithWings Move is just static_cast(x). r is already that type. How would move change anything? – notaorb Jun 07 '20 at 17:21
  • @notaorb Because of how expressions work. An expression has a type, and it has a value category. Just writing `r` is an lvalue expression of type `int`. Writing `std::move(r)` is an rvalue expression of type `int`. You're only considering types; and you're missing a few things there too: move does return `int&&`, but expressions drop references before further processing. – Asteroids With Wings Jun 07 '20 at 17:22
  • (after using them to help determine the value category) – Asteroids With Wings Jun 07 '20 at 17:28
  • @AsteroidsWithWings ok, that's an rvalue reference. Thanks for pointing out. I wanted to show that there is no such thing as reference to reference in C++. – armagedescu Jun 07 '20 at 17:46