简体   繁体   中英

Why r-value reference to pointer to const initialized with pointer to non-const doesn't create an temporary and bind it with it?

If we want to initialize an reference with an different type, we need to make it const (const type*) so that an temporary can be generated implicit and the reference binded to with. Alternativaly, we can use r-value references and achieve the same [1]:

Rvalue references can be used to extend the lifetimes of temporary objects (note, lvalue references to const can extend the lifetimes of temporary objects too, but they are not modifiable through them) :

[...]

Samples

Case 1

double x = 10;

int &ref = x; //compiler error (expected)

Case 2

double x = 10;
const int &ref = x; //ok

Case 3

double x = 10;
int &&ref = x; //ok

If we try to do the same with reference to const pointer (const type* &) and initialize it with non-const pointer (type*), different than I expected, only the case 2 works. Why the case 3 leads to compiler error? Why the temporary isn't generated?

Case 1

int x = 10;

int *pX = &x;

const int* &ref = pX; //compiler error (expected)

Case 2

int x = 10;
int *pX = &x;
const int* const &ref = pX; //ok (expected)

Case 3

int x = 10;
int *pX = &x;
const int* &&ref = pX; //compiler error (why?)

In gcc 12.1.0 and clang 14.0.4 with flag -std=c++20 (and some others) the case 3 above don't compile.

  • gcc : 'error: cannot bind rvalue reference of type 'const int*&&' to lvalue of type 'int*''
  • clang: 'error: rvalue reference to type 'const int *' cannot bind to lvalue of type 'int *'

Why in the case of int&, int&&, etc., all worked well, and in this case with pointer there was compiler error? Is there some imprecision in my current knowledge? (I'm novice)

If we do the same with an pr-value (int*), everything works well

Case 3

int x = 10;
//int *pX = &x;
const int* &&ref = &x; //ok (why?)

Related questions:

< Non-const reference to a non-const pointer pointing to the const object >

< const reference to a pointer not behaving as expected >

  • Similar questions but both suggest using reference to const (type* const &). I wonder why r-value reference dont works with pointer, but work with int, etc., and that wasn't asked.

< What does T&& (double ampersand) mean in C++11? >

  • r-value reference (&&)

References

[1] https://en.cppreference.com/w/cpp/language/reference

The standard has a concept of two types being reference-related . This is fulfilled by two types being similar , which basically means if they are the same type with the same number of pointers but possibly different cv-qualifiers (eg, int and const int are similar, int* const ** volatile and volatile int** const * are similar, and crucially int* and const int* are similar).

The standard says that ( [dcl.init.ref]p(5.4.4) ):

If T1 is reference-related to T2 :

  • if the reference is an rvalue reference, the initializer expression shall not be an lvalue.

And since const int* &&ref = pX; , an rvalue reference, and T1 = const int* is reference-related to T2 = decltype(pX) = int* , this applies and so this just isn't allowed. const int* &&ref = std::move(pX); doesn't run into this issue, since the initializer is no longer an lvalue. And of course, explicitly doing the const conversion const int* &&ref = (const int*) pX; also works.

Presumably, this is so const T x; T&& y = x; const T x; T&& y = x; (binding y to a temporary copy of x ) isn't allowed, but by a quirk of the standard it also extends to pointers.

As you've noted, when an lvalue is used in a context that requires a different type, C-family languages will convert the referenced value to a temporary rvalue of the required type if that's possible.

Also as noted, in the specific case of reference initialization, rvalue temporaries can be given longer lives; the compiler has to have some value to refer to, what possible argument could there be for not accepting this temporary value you just constructed? (Let's just ignore "desperately confusing the uninitiated?").

double x = 10;
const int &ref = x; //ok  // <== editorial comment: legal, but no, not ok

That's not strictly illegal, and to make it illegal there'd have to be a special rule to distinguish it from initialization of that same ref declaration as a function argument. The open-code case doesn't lead to any damage either, it's not worth writing a specific rule to forbid, because why would anyone ever do that? And if they try, well, can we call it a teachable moment?

 int x = 10; int *pX = &x; const int* &&ref = pX; //compiler error (why?)

This is not the same as the case above. You specifically declared a reference to a temporary, and there's no temporary.

Reference syntax was invented to make pass-by-reference work. A constant lvalue reference to a temporary doesn't lead to trouble, a non-constant reference to a temporary can: the receiver might be treating it as an out-parameter, and the caller might not notice the conversion that means a temporary is being passed.

rvalue references are sausage-making devices added later after nobody could find a better plan. They're specifically for values known to be temporaries, among other reasons so the receiver can be certain of ownership. They're not ruled out in open code same as before, but outside the parameter-passing context they look as bafflingly out of place and behave as strangely as a tunafish flopping in your driveway.

Don't, just don't, try to declare rvalue references in open code. That is a rabbit hole desperate criminals like library implementors must descend, because they've been promised freedom for themselves and their families if their mission succeeds. Rvalue references may not be initialized from lvalue references because if they were, it would break function call overloads on temporaries.

The reason why

int x = 10;
int *pX = &x;
const int* &&ref = pX; //compiler error (why?)

doesn't compile is for the same reason as why

int x = 10;
int&& y = x; // doesn't compile
const int&& z =x; // doesn't compile

That's cause you try to assign an l-value reference to an r-value reference. That's straight up forbidden.

The reason why

 double x = 10;
 int&& z  = x;

compiles is because here one doesn't try to assign l-value reference to an r-value reference; this part is inherently impossible so compiler first converts double to int and now your equation becomes binding an r-value reference to a temporary which totally makes sense for compiler.

I am sure you can figure out now why Case 3 compiles

  int x = 10;
  const int* &&ref = &x;

that's cause &x is a temporary and can be bound to an r-value reference.

Generally speaking, this approach is ill advised as it is confusing, misleading, and can lead to a ton of bugs in the code. Consider asking a question about better design. It might be a question towards codereview or some other site.

Note: please don't confuse it with auto&& x = y; That's something different and pretty much always compiles.

There is a big difference between:

const int *x

which is a pointer to a constant int , and a

int * const x

which is a constant pointer to an int . This is something completely different.

For starters, a pointer to an int doesn't have many implicit conversions, so it's not like it can be implicitly converted to some other, different, kind of a pointer.

But, if you want to implictly convert the pointer to a constant pointer, sure:

int x = 10;
int *pX = &x;
int * const &&ref = std::move(pX);

Note: std::move must be used for the same reason why the following doesn't work:

int x=10;
int &&y=x;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM