From the Working Draft, Standard for Programming Language C++, [basic.lval/5]:
The result of a glvalue is the entity denoted by the expression. The result of a prvalue is the value that the expression stores into its context; a prvalue that has type cv void has no result. A prvalue whose result is the value V is sometimes said to have or name the value V. The result object of a prvalue is the object initialized by the prvalue; a non-discarded prvalue that is used to compute the value of an operand of a built-in operator or a prvalue that has type cv void has no result object.
[Note 4: Except when the prvalue is the operand of a decltype-specifier, a prvalue of class or array type always has a result object. For a discarded prvalue that has type other than cv void, a temporary object is materialized; see [expr.context]. — end note]
What is the difference between initializing an object and computing a value?
Some examples of each would be appreciated.
Related
From cppreference, I am trying to understand expressions that yield xvalues, and I ended up with this summary:
The following expressions are xvalue expressions:
...
any expression that designates a temporary object, after temporary
materialization.
Temporary materialization is:
A prvalue of any complete type T can be converted to an xvalue of the
same type T. This conversion initializes a temporary object 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.
And per my understanding from the above quote, temporary materialization involves converting the prvalue into an xvalue to initialize the created temporary; and that's mean that whenever prvalue is materialized, an xvalue expression appears. So I found myself have to understand when exactly a prvalue is materialized. then I have checked this from cppreference:
Temporary materialization occurs in the following situations:
1- when binding a reference to a prvalue;
2- when performing a member access on a class prvalue;
3- when performing an array-to-pointer conversion
or subscripting on an array prvalue;
4- when initializing an object of type std::initializer_list from a braced-init-list;
5- when typeid is applied to a prvalue
6- when sizeof is applied to a prvalue
7- when a prvalue appears as a discarded-value
expression.
Note that temporary materialization does not occur when initializing an object from a prvalue of the same type (by direct-initialization or copy-initialization): such object is initialized directly from the initializer. This ensures "guaranteed copy elision".
Can anyone help me with simple examples how xvalue expression are involved in the situation 3, 4 and 7.
situation 3, 4 and 7.
7 (discarded expression) is the easiest:
42; // materialize and discard
std::string{"abc"}; // materialize and discard
3 (doing things to array rvalue) requires knowing how to make them
using arr_t = int[2][3];
int a = arr_t{}[0][0]; // have to materialize to be able to subscript
4 (making an std::initializer_list) is what it says on the tin
std::initializer_list<std::string>{
"abc"s,
"def"s
}; // have to materialize the two strings
// to place them in the secret array pointed to by the list
I wanted a clarification regarding C++ value categories.
struct Foo {...};
void do_something(Foo{});
Is the Foo{} above a r-value or an x-value?
I understand that there is a hierarchy of value categories, and that r-values are actually either an x-value or a pr-value. I also know that the standard says that "temporary materialization is an x-value" but I wasn't sure if the creation of a temporary fits under this definition.
But what I wasn't sure about was whether gl-value and r-value were "abstract" categories in the hierarchy, with the leafs (l-value, x-value, and pr-value) being the actual implementations.
Could someone explain this for me?
From https://eel.is/c++draft/basic.lval (accessed 04/02/2021):
Every expression belongs to exactly one of the fundamental
classifications in this taxonomy: lvalue, xvalue, or prvalue. This
property of an expression is called its value category.
This answers your question of whether gl-value and r-value are categories of values: they are.
On what kind of value your particular case is, let's look at xvalue:
An xvalue is a glvalue that denotes an object whose resources can be reused (usually because it is near the end of its lifetime).
[ ... snip ... ]
# [Note 3: An expression is an xvalue if it is:
(4.1) the result of calling a function, whether implicitly or explicitly, whose return
type is an rvalue reference to object type ([expr.call]),
(4.2) a cast to an rvalue reference to object type ([expr.type.conv],
[expr.dynamic.cast], [expr.static.cast] [expr.reinterpret.cast],
[expr.const.cast], [expr.cast]),
(4.3) a subscripting operation with
an xvalue array operand ([expr.sub]),
(4.4) a class member access
expression designating a non-static data member of non-reference type
in which the object expression is an xvalue ([expr.ref]), or
(4.5) a
.* pointer-to-member expression in which the first operand is an
xvalue and the second operand is a pointer to data member
([expr.mptr.oper]).
Your case does not appear to fit any of those. Now let's look at prvalue:
A prvalue is an expression whose evaluation initializes an object or computes the value of an operand of an operator, as specified by the context in which it appears, or an expression that has type cv void.
Your case appears to be initialising an object. For this reason, I would say it's a prvalue.
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.
struct S{
int a[3] = {1,2,3};
};
S&& f(){return S();}
&f().a; //[Error] taking address of xvalue (rvalue reference)
&f().a[0]; //ok in GCC 5.1.0 and Clang 3.6.0
S s;
&static_cast<S&&>(s).a; //[Error] taking address of xvalue (rvalue reference)
&static_cast<S&&>(s).a[0]; //ok in GCC 5.1.0 and Clang 3.6.0
5.7 An expression is an xvalue if it is:
(7.1) — the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type,
(7.2) — a cast to an rvalue reference to object type,
(7.3) — a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or
(7.4) — a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.
5.2.1 Subscripting
A postfix expression followed by an expression in square brackets is a postfix expression. One of the expressions
shall have the type “array of T” or “pointer to T” and the other shall have unscoped enumeration
or integral type. The result is of type “T”. The type “T” shall be a completely-defined object type. The expression E1[E2] is identical (by definition) to *((E1)+(E2))<<*t [ Note: see 5.3 and 5.7 for details of * and
+ and 8.3.4 for details of arrays. —end note ], except that in the case of an array operand, the result is an lvalue if that operand is an lvalue and an xvalue otherwise.
So, is f().a[0] an xvalue?
I think f().a[0] should be an xvalue.
[Edit1]
Ignoring &f().a; and &f().a[0]; because 12.2[class.temporary]p5.2
The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not
extended; the temporary is destroyed at the end of the full-expression in the return statement
static_cast<S&&>(s).a is an xvalue(7.2 and 7.3).
" except that in the case of an array operand, the result is an lvalue if that operand is an lvalue and an xvalue otherwise."
So I think static_cast<S&&>(s).a[0] should be an xvalue, but
&static_cast<S&&>(s).a[0]; //ok in GCC 5.1.0 and Clang 3.6.0
Questing:
Am I wrong? If I am wrong, show me an example that subscripting an array results an xvalue.
As far as I can tell you are indeed correct, this looks a "bug", although to be fair this changed recently with CWG defect 1213 which says:
Because the subscripting operation is defined as indirection through a pointer value, the result of a subscript operator applied to an xvalue array is an lvalue, not an xvalue. This could be surprising to some.
and this changed section 5.2.1 [expr.sub] as follows:
A postfix expression followed by an expression in square brackets is a
postfix expression. One of the expressions shall have the type
“array of T” or “pointer to T” and the other shall have unscoped enumeration or integral type. The result is an lvalue of type
“T.” The type “T” shall be a completely-defined object type.62 The
expression E1[E2] is identical (by definition) to *((E1)+(E2)) [Note:
see 5.3 [expr.unary] and 5.7 [expr.add] for details of * and + and
8.3.4 [dcl.array] for details of arrays. —end note], except that in the case of an array operand, the result is an lvalue if that operand
is an lvalue and an xvalue otherwise.
So indeed the result of f().a[0]; and static_cast<S&&>(s).a[0] should be xvalues.
This defect did not have a proposed resolution until December 2012 and clangs defect report support lists the support of that defect report as unknown so most likely the implementers have not gotten to fixing this defect yet.
Update
Filed a clang bug report: Subscript operator applied to an temporary array results in an lvalue.
I could test in Clang 3.4.1 std=c++11.
Here are my conclusions :
int i = f().a[0] would be correct : you get a reference to a temporary struct, the lifetime of the temporary is extended for the duration of the reference, you take a value : fine.
int *i = &f().a[0] is accepted by the compiler. However, the warning on f saying that you are returning reference to local temporary object makes sense here. The lifetime of the temporary object is extended for the duration of the reference : here the time to copy the address. As soon as you have taken the address of a, the containing object vanishes and you only have a dangling reference.
int *i = f().a is exactly same case as previous one.
But when you do &f().a, you are taking the address of an rvalue of type 'int [3]', and it does not make sense to take such an address : you can only take its value.
Let's go one step further :
S s = f(); is correct. You get a reference to a temporary struct, the lifetime of the temporary is extended for the duration of the reference, you take a value : fine.
Now &s.a[0] is a well defined pointer to int, as is int *ix2 = &static_cast<S&&>(s).a[0];
You can even write : int (*ix3)[3] = &s.a; to take the address of an array to 3 int, but still for same reason, you cannot write &static_cast<S&&>(s).a because you would take the address of an rvalue of type 'int [3]'
TL/DR
With S s = f(); s.a is a well defined rvalue, s.a[0] is a well defined lvalue (you can write s.a[0] = 5;).
f().s is a rvalue, but using it will invoke UB, because it ends in a reference to a temporary object that will be destroyed before you can use it.
f().s[0] can be used as a well defined rvalue. You can use it as a lvalue, but for the same reason as above, it would invoke UB.
int x = 8;
int y = x ;
Here how a lvalue can be act as rvalue ? I know this is a silly question , but i just want to make my concepts clear on rvalue and lvalue .
Through an lvalue to rvalue conversion.
You can use an lvalue almost anywhere where an rvalue is required and an implicit lvalue to rvalue conversion will occur automatically.
Informally this conversion is "evaluating" or "taking the value of" the object that the lvalue refers to. This isn't strictly true in all cases; in unevaluated contexts, such as the operand of sizeof, the value of the object won't be used.
ISO/IEC 14882:2011 4.1 [conv.lval]:
A glvalue (3.10) of a non-function, non-array type T can be converted to a prvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.
An lvalue is any value that can appear on the left hand side of an assignment. Something that can be assigned to, like the variable x in this case. Rvalues are things that have a value, like 8 or getting the value of x. You can think of lvalues as a subset of rvalues, that is all lvalues can have their value queried and used as an rvalue.