Does the Standard Mandate an Lvalue-To-Rvalue Conversion of the Pointer Variable When Applying Indirection

Does the standard mandate an lvalue-to-rvalue conversion of the pointer variable when applying indirection?

I have converted the update section in my question to an answer since at this point it seems to be the answer, albeit an unsatisfactory one that my question is unanswerable:

dyp pointed me to two relevant threads that cover very similar ground:

  • What is the value category of the operands of C++ operators when unspecified?
  • Does initialization entail lvalue-to-rvalue conversion? Is int x = x; UB?

The consensus seems to be that the standard is ill-specified and therefore can not provide the answer I am looking for, Joseph Mansfield posted a defect report on this lack of specification, and it looks like it is still open and it is not clear when it may be clarified.

There are a few common sense arguments to be made as to the intent of the standard. One can argue Logicially, an operand is a prvalue if the operation requires using the value of that operand. Another argument is that if we look back to the C99 draft standard says an lvalue to rvalue conversion is done by default and the exceptions are noted. The relevant section from the draft C99 standard is 6.3.2.1 Lvalues, arrays, and function designators paragraph 2 which says:

Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator,
an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue). […]

which basically says with some exceptions an operand is converted to the value stored and since indirection is not an exception if this is clarified to also be the case in C++ as well then it would indeed make the answer to my question yes.

As I attempted to clarify the proof of undefined behavior was less important than clarifying whether a lvalue-to-rvalue conversion is mandated. If we want to prove undefined behavior we have alternate approaches. Jerry’s approach is a common sense one and in that indirection requires that the expression be a pointer to an object or function and an indeterminate value will only by accident point to a valid object. In general the draft C++ standard does not give an explicit statement to say using an indeterminate value is undefined, unlike the C99 draft standard In C++11 and back the standard does not give an explicit statement to say using an indeterminate value is undefined. The exception being iterators and by extension pointers we do have the concept of singular value and we are told in section 24.2.1 that:

[…][ Example: After the declaration of an uninitialized pointer x (as with int* x;), x must always be assumed to have a singular value of a pointer. —end example ] […] Dereferenceable values are always non-singular.

and:

An invalid iterator is an iterator that may be singular.268

and footnote 268 says:

This definition applies to pointers, since pointers are iterators. The effect of dereferencing an iterator that has been invalidated is undefined.

In C++1y the language changes and we do have an explicit statement making the use of an intermediate value undefined with some narrow exceptions.

Does lvalue-to-rvalue conversion is applied to non-type lvalue template argument?

The meaning of [expr.const]/10 is that whenever an expression appears in a context that requires a "converted constant expression of type T", that expression is "implicitly converted to type T" and the additional restrictions in [expr.const]/10 shall apply.

So s is not "converted to a prvalue before any implicit conversions are applied to it". Rather, s is implicitly converted to type S, and this implicit conversion might involve some standard and/or user-defined conversions, such as an lvalue-to-rvalue conversion. To know whether or not it does, we would have to look at the rules for implicit conversions, which are in [conv.general]. In particular [conv.general]/6 states that

The effect of any implicit conversion is the same as performing the corresponding declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is an lvalue reference type or an rvalue reference to function type ([dcl.ref]), an xvalue if T is an rvalue reference to object type, and a prvalue otherwise.
The expression E is used as a glvalue if and only if the initialization uses it as a glvalue.

I believe the wording here is a relic of older standard editions. What it should say (in modern language) is that when T is a non-reference type, an implicit conversion of E to T yields a prvalue that initializes its result object (call it t) as if by T t = E;.

So we consider S t = s; What does this initialization do? It calls the copy constructor of S to initialize t; [dcl.init.general]/16.6.2.1. There is no lvalue-to-rvalue conversion in this scenario. (Lvalue-to-rvalue conversions on class types are rare, but do occur in some places in the language, e.g., [expr.call]/12, [expr.cond]/7. If an lvalue-to-rvalue conversion were performed on s, it would also call the copy constructor; [conv.lval]/3.2. But in this particular case, the rules of the language do not require an lvalue-to-rvalue conversion.)

Consequently, the result of using s as a template argument for a template parameter of type S is that a prvalue is generated that initializes its result object by calling the copy constructor with s as the argument. (This particular copy constructor is implicitly defined to not do anything, since there are no subobjects to copy.)

This answers your question regarding whether an lvalue-to-rvalue conversion is applied. (You might be wondering what actually happens to the prvalue and whether the copy constructor actually gets called, but that's a separate topic that I don't want to get into right now, because it would make this answer too long.)

As for whether s is constant-initialized, yes it is. It satisfies (2.1) (since it has an initializer) and (2.2) since the full-expression of its initialization is a constant expression (there is nothing to stop it from being a constant expression, since it does nothing other than calling the constexpr default constructor, which itself does nothing). I'm not sure how this is relevant to your other question, though.

Lvalue to rvalue conversion with integer pointer

The standard states that this is ill-formed explicitly; lvalue-to-rvalue conversion won't (shouldn't) be considered.

From [dcl.init.ref]/5.4.4:

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

[ Example:

double d2 = 1.0;
double&& rrd2 = d2; // error: initializer is lvalue of related type

Does lvalue-to-rvalue conversion ever happen to class types?

An example is volatile objects in discarded-value expressions:

struct A {};

void f()
{
volatile A a;
a;
}

According to [expr.context]/2:

In some contexts, an expression only appears for its side effects.
Such an expression is called a discarded-value expression. The
array-to-pointer and function-to-pointer standard conversions are not
applied. The lvalue-to-rvalue conversion is applied if and only if
the expression is a glvalue of volatile-qualified type and it is one
of the following
:

  • ...
  • id-expression,
  • ...

Lvalue-to-rvalue conversion is applied to a.

Regarding lvalue-to-rvalue conversion, when is it required?

So, this is generally one of those inferred and ill-specified parts of the standard; However, in 3.10

[ Note: some built-in operators expect lvalue operands. [ Example: built-in assignment operators all expect their left-hand operands to be lvalues. — end example ] Other built-in operators yield rvalues, and some expect them. [ Example: the unary and binary + operators expect rvalue arguments and yield rvalue results. — end example ] The discussion of each built-in operator in clause 5 indicates whether it expects lvalue operands and whether it yields an lvalue. — end note ]

Notice the poorly specified language "in clause 5 indicates whether it expects lvalue operands and whether it yields an lvalue".

Examination of Chapter 5 indicates that every case where an expression needs or returns an lvalue is enumerated, however very few cases dealing specifically with rvalues are enumerated, I think it's then assumed that the rest is assumed to be rvalues.

I also suspect it's poorly specified because from the perspective of the standard, it's not particularly important if the conversion is done implicitly or explicitly by the operator, regardless, the behavior should be the consistent and well-behaved.

What standard clause mandates this lvalue-to-rvalue conversion?

I find it easier (if maybe not 100% precise) to think of lvalue-s as real objects and rvalue-s as the value stored in the object. The expression x is an lvalue expression that refers to the object x defined in the first line, but when used as the right hand side of an assignment to a type that is not a user defined type the actual value is read, and that is where the conversion from lvalue to rvalue is performed: reading the contents of the object.

As to the specific clause in the standard that dictates that conversion... well, the closest that I can think is 4.1 [conv.lvalue]/2 (Lvalue to Rvalue conversion):

The value contained in the object indicated by the lvalue is the rvalue result.

The requirement that the right hand side of the assignment is an rvalue is either implicit or missing from 5.17 [expr.ass], but that is the case or else the following expression would be an error since the rhs is an rvalue and there is no rvalue-to-lvalue conversion:

int x = 5;

EDIT: For initialization, 8.5 [dcl.init]/14, last bullet (which refers to fundamental types) states (emphasis mine):

  • Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. [...]

That value there means that the lvalue expression in your example is read (i.e. converted to an rvalue). At any rate the previous paragraph that referred to assignment could be applied here: if initialization required an lvalue rather than an rvalue, the expression int i = 0; would be ill-formed.

Applying the lvalue-to-rvalue conversion to variable `x` does not yield a constant expression in the code below. Why is that?

Applying the lvalue-to-rvalue conversion to x reads the value of the mutable global variable globx, which makes it not a constant expression as the value of globx is subject to change (and, even if it were const, there would be the issue of its value not being known at compile time). Of course, this is not surprising: no one would expect int{x} to be a constant expression. The DR points out that this fact should, nonetheless, not cause the odr-use of x and thus the code should be well-formed: since x has constant initialization, the compiler can just translate &x into &globx.



Related Topics



Leave a reply



Submit