2

I am trying to understand how reference initialization. For example, let's look at a typical example.

double val = 4.55;
const int &ref = val;

I can think of 2 possibilities of what is happening in the above snippet.

Possibility 1

The usual explanation is given as follows:

Here a temporary(prvalue) of type int with value 4 is created and then the reference ref is bound to this temporary(prvalue) int object instead of binding to the variable val directly. This happens because the type of the variable val on the right hand side is double while on the left hand side we have a reference to int. But for binding a reference to a variable the types should match. Moreover, the lifetime of the temporary prvalue is extended.

Possibility 2

I think there is another possibility that could happen which is as follows:

Here a temporary(prvalue) of type int with value 4 is created. But since const int &ref expects a glvalue and currently we've a prvalue, the temporary materialization kicks in and so the prvalue is converted to an xvalue. Then the reference ref is bound to this materialized xvalue(since xvalue is also a glvalue) instead of binding to the variable val directly. This happens because the type of the variable val on the right hand side is double while on the left hand side we have a reference to int. But for binding a reference to a variable the types should match. Moreover, the lifetime of the materialized temporary xvalue is extended.

My questions are:

  1. Which of the above explanation is correct according to the C++11 standard. I am open to accept that none of the explanation above is correct in which case what is the correct explanation.
  2. Which of the above explanation is correct according to the C++17 standard. I am open to accept that none of the explanation above is correct in which case what is the correct explanation.
  3. I am also confused to whether a prvalue in the first step of both of the possibilities above, is actually a temporary object? Or the xvalue is the actual object. I mean do we have 2 temporary objects, like the first one due to "conversion to prvalue" and second one due to the "prvalue to xvalue" conversion(temporary materiliazation). Or do we only have one temporary which is due to the "prvalue to xvalue" temporary materialization.

PS: I am not looking for a way to solve this. For example, i know that i can simply write: const double &ref = val;. My aim is to understand what is happening according to C++11 and C++17 standards.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • In C++17 definitely (2) is correct. In C++17 prvalues are not actual objects, so references can't point to them. – HolyBlackCat Apr 09 '22 at 08:46
  • You might find interest in my recent [question](https://stackoverflow.com/questions/71740068/why-does-temporary-object-gets-created-here). It's the same topic, but with user defined types. – domdrag Apr 11 '22 at 17:53
  • @domdrag Sure, will check it out. – Jason Apr 11 '22 at 17:58
  • In this example the referenced temporary object should be of type `const int`. The `const` matters on whether the object is immutable or usable in constant expressions. – F.v.S. May 11 '22 at 03:50

3 Answers3

4

val in const int &ref = val; is a lvalue, not a prvalue. Even if it is converted to a prvalue, this doesn't mean creation of a temporary in either C++11 or C++17. In C++17 this isn't the case since only prvalue-to-xvalue conversion (and some special cases not relevant here) creates a temporary, while in C++11 [conv.lval]/2 says that only for class types lvalue-to-rvalue conversion creates a temporary.

The initialization of a reference is explained in [dcl.init.ref].

In C++11, all previous cases fall through and so according to [dcl.init.ref]/5.2.2 a temporary of the destination type is created and initialized by copy-initialization from the initializer expression and the reference is bound to that temporary. In this copy-initialization the lvalue val is converted to a prvalue of type double and then to a prvalue of type int, neither of these steps creating additional temporaries.

In C++17, all cases fall through until [dcl.init.ref]/5.2.2, which states that the initializer expression is first implicitly converted to a prvalue of the destination type, which does not imply creation of a temporary, and then the temporary materialization conversion (prvalue-to-xvalue conversion) is applied and the reference bound to the result, i.e. the xvalue referring to the temporary.

In the end there is always exactly one temporary, the one created according to the rule in [dcl.init.ref].

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • While explaining C++11 case, you wrote: *"a temporary of the destination type is created by copy-initialization from the initializer expression..."*. Then you wrote: *" neither of these steps creating additional temporaries."* My question is that why did you write "neither of these steps creates additional temporaries? I mean i can understand that the conversion from a `double` prvalue to a `int` prvalue does not create additional prvalue. But The creation from `val` lvalue to `double` prvalue does create a temporary. – Jason Apr 09 '22 at 09:32
  • continued... So it should be: *"conversion from `double` prvalue to `int` prvalue"* does not create additional temporary. – Jason Apr 09 '22 at 09:32
  • @Anya No, as I referenced at the beginning, the lvalue-to-rvalue conversion doesn't create a temporary for non-class types. See last sentence of [conv.lval]/2. But even if it did it would be irrelevant, since that temporary could never be referenced. – user17732522 Apr 09 '22 at 09:34
  • Oh, ok so the temporary comes from copy-initialization instead of lvalue to rvalue conversion in C++11 right? – Jason Apr 09 '22 at 09:35
  • @Anya It comes from [dcl.init.ref] saying that one is created for the purpose of binding the reference to it. Copy-initialization is then just the method chosen to initialize this temporary. – user17732522 Apr 09 '22 at 09:36
  • Ok i get that one is created for the purpose of binding the reference to it. I am asking about at exactly which step does the temporary gets created/materialized in C++11. For example, it looks to me that copy-initialization is the creation of temporary in C++11? I mean you said in your answer that *"a temporary of the destination type(`double`) is created by copy-initialization"* – Jason Apr 09 '22 at 09:38
  • I am not sure what exact nuance you are trying to figure out here. The copy-initialization is the initialization of the temporary object. If you want to know when the lifetime or storage duration of the temporary begins exactly, I am not sure that this is well-specified, but it is also irrelevant. – user17732522 Apr 09 '22 at 09:43
  • @Anya I made that sentence a bit more clear. – user17732522 Apr 09 '22 at 09:44
  • I am getting confused because of the statement *"neither of these steps creating additionaltemporaries."*. For example, currently this is my understanding of C++11 case. **Step 1**: A temporary object of destination type `double`is created using the initializer `val` using copy-initialization. The result of this step 1 is a prvalue `double`. **Step 2** Next,this prvalue `double` temporary is converted to a prvalue `int`.In this step2 there is no creating of temporary. **Step3** `ref` is bound to the prvalue `int` created in step3.Can you please explain in steps in your answer as well. – Jason Apr 09 '22 at 09:58
  • @Anya Step 1 does not create a temporary. It is just lvalue-to-rvalue conversion with the result a prvalue containing the value of `val`. The temporary creation happens as part of step 3 which creates and initializes it with the value obtained in step 2. The reference is bound to that temporary. The "destination type" in my answer is the type of the reference variable with the reference removed, so `const int`, not `double`. – user17732522 Apr 09 '22 at 10:13
  • Ok, now it is more clear. +1 for the clarification. So these are the steps. **Step1** Lvalue(`double` `val`) to rvalue(`double`) conversion takes place. **Step2** The prvalue obtained in the last step1 is converted to prvalue `int`. **Step3** Now copy-initialization happens and a temporary prvalue `int` object is created and initialized from the `int` prvalue obtained in last step2. **Step4** `ref` is bound to this prvalue `int` temporary object obtained in the last step3. Is this the correct understanding for C++11? – Jason Apr 09 '22 at 10:24
  • @Anya Seems about right. – user17732522 Apr 09 '22 at 10:26
  • But this also means(according to our discussion above) that `ref` is finally bound to a **prvalue** and not a **glvalue**. I read that a glvalue is needed for `const int &ref`. But in our discussion above, `ref` seems to be bound to a `prvalue`. How is this possible? I mean does this mean that `const int &ref` do not require a glvalue? – Jason Apr 09 '22 at 10:29
  • Or since `val` is a glvalue, the requirement is already satisfied? – Jason Apr 09 '22 at 10:35
  • Or is glvalue required only in C++17? – Jason Apr 09 '22 at 10:48
  • @Anya In the end the reference should be bound to an object. In this case the referenced standard passages say that the object to which the reference is bound is the temporary. I think there is nothing wrong with saying that the reference is bound to the (result of the) prvalue in C++11 with the understanding that this really refers to the temporary created according to the rules above. – user17732522 Apr 09 '22 at 15:43
  • In C++17 there is always a prvalue->xvalue conversion involved. A prvalue expression doesn't denote a temporary before that conversion, so it may make sense to say that references can only bind to (the result of) glvalues. But if we say "reference bound to the prvalue" I think that's also fine, the temporary materialization conversion is just implied as part of the binding. – user17732522 Apr 09 '22 at 15:43
  • But all of that is getting very pedantic about the exact meaning of the technical terms. I am probably not using the terminology 100% accurate in my previous comments, nor do I think they are usually used 100% accurately. – user17732522 Apr 09 '22 at 15:49
  • I just have an unrelated question instead of posting a new question. If `T1` is **indirect** base of `T3`, does this means that `T1` is reference-related to `T2`? So that if I have `T1& ref = t3` (where `t3` is lvalue of type `T3`), then it's said that `ref` is binding _directly_ to `T1` subobject of `t3`? – mada Aug 09 '22 at 11:43
  • @John This sounds like a new question. I don't see it being about this answer. But more or less I guess that is correct. – user17732522 Aug 09 '22 at 18:58
2

Lets look at each of the cases(C++11 vs C++17) separately.

C++11

From decl.init.ref 5.2.2:

Otherwise, a temporary of type “ cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization ([dcl.init]). The reference is then bound to the temporary.

One more important thing to note is that from basic.lval#4:

Class prvalues can have cv-qualified types; non-class prvalues always have cv-unqualified types...

When applied to your example, this means that a temporary of type int is created and is initialized from the initializer expression val using the rules for a non-reference copy-initialization. The temporary int so created has the value category of prvalue if/when used as an expression.

Next, the reference ref is then bound to the temporary int created which has value 4. Thus,

double val = 4.55;
const int &ref = val; // ref refers to temporary with value 4

C++17

From decl.init.ref 5.2.2.2:

Otherwise, the initializer expression is implicitly converted to a prvalue of type “cv1 T1”. The temporary materialization conversion is applied and the reference is bound to the result.

When applied to your example, this means that the initializer expression val is implicitly converted to a prvalue of type const int. Now we currently have a prvalue const int. But before temporary materialization is applied, expr 6 kicks in which says:

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

This means before temporary materialization could happen, the prvalue const int is adjusted to prvalue int.

Finally, temporary materialization is applied and and ref is bound to the resulting xvalue.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • 1
    In C++11 it says that the type of the temporary is `cv1 T1`, so the type will be `const int`, not `int`. In C++17 this was unintentionally changed to `int` for the reason you gave, but reverted back with a defect report ([CWG issue 2481](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2481)). – user17732522 Apr 12 '22 at 08:16
  • @user17732522 In the same [document](https://timsong-cpp.github.io/cppwp/n3337/basic.lval#4) for C++11, it is said that: *"non-class prvalues always have cv-unqualified types."*. So what should happen to the `const int` prvalue that we're talking about? Is this a defect/contradiction or the `const int` is just again changed to `int`? Or am i reading [this](https://timsong-cpp.github.io/cppwp/n3337/basic.lval#4) wrong. I do think that due to *basic.lval#4*, since in the OP's example we have a non-class type so finally we will have a `int` prvalue instead of `const int` prvalue. – Jason Apr 12 '22 at 08:30
  • In your C++11 quote of decl.init.ref 5.2.2 no prvalue is mentioned. It says that a temporary object of the cv-qualified type is created and initialized from the expression, not that the expression is converted to a prvalue. So I don't think that matters. – user17732522 Apr 12 '22 at 18:45
  • @user17732522 But the temporary that was created has the value category of `prvalue` doesn't it? And since it is a prvalue so `basic.lval#4` should apply on it IMO. On the other hand if what you're saying is correct, then the temporary isn't a prvalue when used in an expression. – Jason Apr 13 '22 at 03:45
1

Here's my take for C++17:

double val = 4.55;
const int &ref = val;

We're binding a const int reference to the lvalue expression of type double denoted by val. According to the declaration of references;

Otherwise, the initializer expression is implicitly converted to a prvalue of type “T1”.

we convert the expression val to type int. Note that this implicit type! conversion fits with the draft. Next,

The temporary materialization conversion is applied, considering the type of the prvalue to be “cv1 T1”, ...

in order to apply the temporary materialization (also known as prvalue -> xvalue conversion), we must have fully matched types so the expression is aditionally converted to const int is considered to be const int without any conversions (source). The value category remains the same (prvalue). And finally,

... and the reference is bound to the result.

we have a reference binding of type T to a prvalue of type T which induces temporary materialization (source) and val gets converted to xvalue. The created temporary object is of type const int and value of 4.

EDIT: And to answer your questions, of course. So neither of the given possibilities aren't technically correct for C++17 right at the start. Strictly speaking, neither prvalues nor xvalues are temporary objects. Rather, they are value category of expressions and expression might (or might not) denote a (temporary) object. So technically, xvalues denote temporary objects while prvalues don't. You could still say that xvalues are temporary objects, that's completely fine by me as long as you know what you're talking about.

domdrag
  • 541
  • 1
  • 4
  • 13
  • You are quoting the current standard draft, not the C++17 draft (although effectively the result will be the same, just the wording is a bit different). "_is additionally converted to const int_": No conversion is required. The paragraph you quoted says that the prvalue is simply considered to be `const` qualified. This was actually added because the rvalue-to-xvalue conversion would _drop_ the `const`, not add it. See [CWG issue 2481](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2481). – user17732522 Apr 12 '22 at 08:23