I have read that the code below is valid in C++11:
int && a = 3;
a = 4;
Is it supposed to write 4 in the memory address where the numeric literal 3 is stored? Maybe some compiler optimizations would prevent this from happening, but is it supposed to do so?
When you assign a prvalue that is not of class type to an rvalue reference, a temporary object is created and the reference is bound to that. You are simply modifying the temporary object.
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the reference is an lvalue reference [...]
Otherwise, [...] or the reference shall be an rvalue reference.
If the initializer expression
is an xvalue (but not a bit-field), class prvalue, array prvalue or function lvalue [...], or
has a class type [...]
[...]
Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization (8.5). The reference is then bound to the temporary.
Conceptually, a prvalue is just a value that may or may not have come from some object in memory. Literals don't have a corresponding object in memory, so this rule forces an object to be created.
Related
The following quotes are needed in the question:
[dcl.init.ref]/5:
5- A reference to type “cv1 T1” is initialized by an expression of
type “cv2 T2” as follows:
(5.1) [..]
(5.2) [..]
(5.3) Otherwise, if the initializer expression
(5.3.1) is an rvalue (but not a bit-field) or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
(5.3.2) [..]
then the value of the initializer expression in the first case and the result of the conversion in the second case is called the converted initializer. If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied. In any case, the reference is bound to the resulting glvalue (or to an appropriate base class subobject).
(emphasis mine)
[expr.type]/2:
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.
Consider the following example:
const int&& r1 = 0;
Taking cv1 T1 as const int, and cv2 T2 as int
It's clear that bullet [dcl.init.ref]/(5.3.1) is applied here. The initializer expression is an rvalue (prvalue); and cv1 T1 (const int) is reference-compatible with cv2 T2 (int). And since that the converted initializer is prvalue, its type T4 (int) is adjusted to cv1 T4 (const int). Then, temporary materialization is applied.
But, per [expr.type]/2, before applying temporary materialization conversion, cv1 T4 (const int) becomes int again. Then, by applying temporary materialization, we've got an xvalue denoting an object of type int. Then the reference is bound to the resulting glvalue.
Here's my first question. The reference r1 is reference to const int and the resulting glvalue is an xvalue denoting an object of type int. So how r1, which is of type const int&&, is now binding to an xvalue of type int? Is this valid binding? Is any missing wording? Am I misunderstood/missed something?
Consider another last example:
const int&& r2 = static_cast<int&&>(0);
The same wording as above applies: The initializer expression is an rvalue (xvalue) and cv1 T1 (const int) is reference-compatible with cv2 T2 (int). And since that the converted initializer is an xvalue not prvalue, [conv.qual] or even [conv.rval] is not applied (i.e, the condition "If the converted initializer is a prvalue, ..") isn't satisfied.
I know that [conv.rval] isn't needed here since the initializer expression is already an xvalue, but [conv.qual] is required.
And that's my last question. The reference r2 is reference to const int and the resulting glvalue is an xvalue denoting an object of type int. So how r2, which is of type const int&&, is now binding to an xvalue of type int? Is this valid binding? Is any missing wording? Am I misunderstood/missed something?
Here's my first question. The reference r1 is reference to const int and the resulting glvalue is an xvalue denoting an object of type int. So how r1, which is of type const int&&, is now binding to an xvalue of type int? Is this valid binding?
Yes, that's what happens. I fail to see the issue here.
I know that [conv.rval] isn't needed here since the initializer expression is already an xvalue, but [conv.qual] is required.
No it isn't. Again, I fail to see the issue.
There is, in fact, no rule that says that a reference of type T&&, can only refer to an object whose type is exactly T. A const int&& can refer to an int object. The concept of reference-compatibility was invented in order to describe what types of objects a reference can refer to.
Given
void foo( int&& x ) {
std::cout << &x;
}
This works but what does this address actually represent? Is a temporary int created when foo is called and that is what the address represents? If this is true and if I write int y = 5; foo(static_cast<int&&>(y));, does this cause another temporary to be created or will the compiler intelligently refer to y?
When you take an address of an rvalue reference, it returns a pointer to the object that reference is bound to, like with lvalue references. The object can be a temporary or not (if for example you cast an lvalue to rvalue reference like you do in your code).
int y = 5; foo(static_cast<int&&>(y)); does not create a temporary. This conversion is described in part 5.2.9/3 of the standard:
A glvalue, class prvalue, or array prvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2” if “cv2 T2” is reference-compatible with “cv1 T1”.
A temporary will be created if for example you call foo(1);.
This is a followup to my previous question, where the apparent consensus was that the change in treatment of cv-qualifications of prvalues was just a fairly minor and inconsequential change intended to solve some inconsistencies (e.g. functions returning prvalues and declared with cv-qualified return types).
However, I see another place in the standard that appears to rely on prvalues having cv-qualified types: initialization of const references with prvalues through temporary materialization conversion. The relevant wording can be found in multiple spots in 9.3.3/5
[...] If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied [...]
[...] 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.
The intent is obviously to make sure that when we get to the actual temporary materialization conversion
7.3.4 Temporary materialization conversion
1 A prvalue of type T can be converted to an xvalue of type T. This conversion initializes a temporary object ([class.temporary]) of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, and produces an xvalue denoting the temporary object. [...]
the type T that it receives as input includes the required cv-qualifications.
But how does that cv-qualification survive the 7.2.2/2 in case of non-class non-array prvalue?
7.2.2 Type
2 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.
Or does it?
E.g. what kind of temporary do we get in this example
const int &r = 42;
Is the temporary const or not? Can we do
const_cast<int &>(r) = 101; // Undefined or not?
without triggering undefined behavior? If I'm not mistaken, the original intent was to obtain a const int temporary in such cases. Is it still true? (For class types the answer is clear - we get a const temporary.)
Why are you doubting the language of 7.2.2? This seems pretty unambiguous that cv qualifiers are discarded on non-class, non-array prvalues, so the type T in temporary materialization is a non-const, non-volatile type.
If that weren't the case, then you wouldn't be able to bind prvalues to non-const rvalue references. Yet it seems overwhelmingly likely that the standard was intended to accept programs such as this:
#include <type_traits>
template<typename T> void
f(T &&t)
{
static_assert(std::is_same_v<decltype(t), int&&>);
++t;
}
int
main()
{
f(5);
}
E.g. what kind of temporary do we get in this example const int &r = 42; Is the temporary const or not?
Let's analyze your example to see whether it's const or not. Given this example
const int &r = 42;
The applicable wording from the standard is [dcl.init.ref]/5
A reference to type “cv1 T1” is initialized by an expression of
type “cv2 T2” as follows:
(5.1) [..]
(5.2) [..]
(5.3) Otherwise, if the initializer expression
(5.3.1) is an rvalue (but not a bit-field) or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
(5.3.2) [..]
then the value of the initializer expression in the first case and the result of the conversion in the second case is called the converted initializer. If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied. In any case, the reference is bound to the resulting glvalue (or to an appropriate base class subobject).
It's already known that the initializer expression 42 is a prvalue, and const int (cv1 T1) is reference-compatible with int (cv2 T2). And the converted initializer here is of type int (T4); then it's adjusted, via [conv.qual], to const int (cv1 T4); then temporary materialization ([conv.rval]) gets applied. Note that, [expr.type]/2 doesn't apply here because the initial type of the prvalue is cv-unqualified type:
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.
Note also, it cannot be applied after adjustment to cv1 T4 because cv1 T4 is not the initial type of the prvalue.
So the adjusted prvalue has type const int (cv1 T4) Then, temporary materialization gets applied to this prvalue; [conv.rval]:
A prvalue of type T can be converted to an xvalue of type T. This conversion initializes a temporary object ([class.temporary]) of type T [..]
The const int prvalue gets converted to an xvalue of type const int. And a temporary of type const int gets initialized. So you have an xvalue denoting a temporary of type const int. And the reference r is bound to the resulting glvalue (i.e, r is binding to a xvalue denoting a temporary of type const int).
So as far as I can tell, the temporary that has been created is const-qualified.
Can we do const_cast<int &>(r) = 101; without triggering undefined behavior?
No, this undefined behavior by definition since you're trying to modify (write into) a read-only memory location:
[expr.const.cast]/6: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to data member resulting from a const_cast that casts away a const-qualifier can produce undefined behavior.
[dcl.type.cv]/4: Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const
object during its lifetime (3.8) results in undefined behavior.
I know the standard has an exception about extending the lifetime of temporaries that basically says binding a const reference in a constructor won't extend the lifetime, but does this also apply to literals? For example:
class C {
private:
const int& ref;
public:
C(const int& in)
: ref{in}
{ }
};
If I had a function returning an object of this type
C f() {
C c(2);
return c;
}
Would the value of c.ref be undefined in the caller if I know it's bound to a literal?
No. You will not be able to use the reference after the constructor finishes execution.
When a prvalue of non-class type is bound to a const-reference of that same type, a temporary is always introduced. Thus the constructor's parameter reference will refer to a temporary that is destroyed once the reference goes out of scope. After that happens, the member reference is dangling, and attempting to access the stored value behind that reference results in UB.
[dcl.init.ref]/5:
A reference to type “cv1 T1” is initialized by an expression of
type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and [..]
has a class type (i.e., T2 is a class type) [..]
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference
shall be an rvalue reference.
If the initializer expression
is an xvalue (but not a bit-field), class prvalue, array prvalue or function lvalue and [..]
has a class type [..]
Otherwise: (5.2.2.1)
If T1 is a class type [..]
If T1 is a non-class type, a temporary of type “cv1 T1” is created and copy-initialized (8.5) from the initializer expression.
The reference is then bound to the temporary.
And integer literals are, unsurprisingly, indeed prvalues. [expr.prim.general]/1:
A string literal is an lvalue; all other literals are prvalues.
Finally, in case this is unclear, [class.temporary]/5:
The temporary to which the reference is bound or the temporary that is
the complete object of a subobject to which the reference is bound
persists for the lifetime of the reference except:
A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.
Short answer: Evaluating c.ref will almost certainly be illegal (invoke undefined behavior).
Long answer:
When binding a reference to an integer literal, what you are really doing is the following:
The integer literal refers to what is known as "a value that is not associated with an object".
To bind a reference to it, an object needs to be created that holds the same value. The reason for that is that a reference (or pointer) must always point to an object (which in turn is nothing more than a bit of memory). Therefore, a temporary object is created which holds the value.
Temporary objects are guaranteed to last as long as the expression they are created by is being evaluated. Since your object exists for longer, the temporary object that held your value is being destroyed early and the reference may not be accessed anymore.
Note that if you access c.ref within the expression that created c, you would actually be fine.
Given
void foo( int&& x ) {
std::cout << &x;
}
This works but what does this address actually represent? Is a temporary int created when foo is called and that is what the address represents? If this is true and if I write int y = 5; foo(static_cast<int&&>(y));, does this cause another temporary to be created or will the compiler intelligently refer to y?
When you take an address of an rvalue reference, it returns a pointer to the object that reference is bound to, like with lvalue references. The object can be a temporary or not (if for example you cast an lvalue to rvalue reference like you do in your code).
int y = 5; foo(static_cast<int&&>(y)); does not create a temporary. This conversion is described in part 5.2.9/3 of the standard:
A glvalue, class prvalue, or array prvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2” if “cv2 T2” is reference-compatible with “cv1 T1”.
A temporary will be created if for example you call foo(1);.