9

Given:

int main() {
   int x = 0;
   int y = x; // <---
}

Could someone please tell me which clause of the standard (2003 preferred) mandates the conversion of the expression x from lvalue to rvalue in the initialisation of the object y?

(Or, if I'm mistaken and no such conversion takes place, then I'd like to learn that too!)

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055

5 Answers5

9

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Nice one. I think that this is a pretty solid deduction. :) – Lightness Races in Orbit Jun 17 '11 at 08:38
  • "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" <-- This doesn't entirely follow, logically. "RHS must be an rvalue, or the RHS must be an lvalue (and the latter cannot be true so the first must be)" is what you're saying; looks like a false dichotomy to me? – Lightness Races in Orbit Jun 17 '11 at 08:40
  • @Tomalak: No, the reasoning is that the right hand side of the expression is required to be **either** an lvalue or an rvalue, but not both. Now: *if the right hand side of the expression was required to be an lvalue expression, then `i=5` would fail; that expression is valid (does not fail), ergo the right hand side of the expression cannot be required to be an lvalue*. Since the only other option is an *rvalue* (At least in C++03), then the expression `x = y` takes an lvalue as lhs (this is explicit in the standard) and an rvalue as rhs (I could not find a precise quote for this) – David Rodríguez - dribeas Jun 17 '11 at 10:09
  • 2
    Why can't it be **either** depending on the context? – Lightness Races in Orbit Jun 17 '11 at 10:12
8

I do believe that this is intuitive to some degree (what others already said - the value is needed, so there is an obvious need to convert the object designator to the value contained therein). The best I could come up with, by 4p3:

An expression e can be implicitly converted to a type T if and only if the declaration "T t=e;" is well-formed, for some invented temporary variable t (8.5). The effect of the implicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is a reference type (8.3.2), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.

Note the "if and only if" at the end - the initializer therefor is used as an rvalue, because the initialization uses it as an rvalue (result of the conversion). So by 3.10p7

Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue; see 4.1, 4.2, and 4.3.


EDIT: The paragraph for entering 4p3 can be found at 8.5p16, last bullet:

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

Also note the comments below.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • But is there a conversion at all? You start with an `int` and you end up with an `int`. By that reasoning alone, I'm not sure that the first quote applies. – Lightness Races in Orbit Jun 17 '11 at 00:40
  • @Tomalak A conversion doesn't really start with a type, but with an *expression*. And the expression is converted from an lvalue int to an rvalue of int. – Johannes Schaub - litb Jun 17 '11 at 12:11
  • @litb: That's circular logic. "There's a conversion, because of this rule talking about what happens when a conversion is required." – Lightness Races in Orbit Jun 17 '11 at 12:17
  • @Tomalak I agree that such a circular reasoning can be found. It can be argued to be circular. It can also be argued that that paragraph doesn't talk about *an* "implicit conversion*, but rather about an *implicit conversion sequence*. That is, a sequence of conversions *or* no conversion at all. Otherwise, `0` could not be "implicitly converted" to a prvalue of type `int`, which would be wrong because `int a = 0` is well-formed. I think this is a reasonable assumption to put into the wording - the paragraph obviously doesn't preclude sequences that contain two or more conversions. – Johannes Schaub - litb Jun 17 '11 at 12:24
  • @litb: No, but it also doesn't make any mandate that the conversion sequence comes into the picture in the first place, in the given scenario. You have the induction step sorted out, but are still missing a base case. :) – Lightness Races in Orbit Jun 17 '11 at 12:25
2

Is this what you're looking for:

§3.10/7

Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue; see 4.1, 4.2, and 4.3.

And I think when you write int y = x, it basically copies the value contained in the object x which is a lvalue, but the value itself is an rvalue, hence the context expects an rvalue.

§4.1/2 says,

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

Maybe these two quotations clarify your doubt. Correct me if my understanding is wrong. I would like to learn new things.


@Tomalak's comment:

My problem with this is that int& y = x; is valid, so in this case of course x may not be an rvalue. I don't know how irrelevant the difference in my example makes that, though

Well int &y = x does NOT copy the value. It just creates an alias of the object itself. But as I previously said int y = x, basically copies the value which is an rvalue. Hence, the context expects an rvalue, as a copying is being done here.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
2

The initializer has the follwing grammar:

initializer:
        = initializer-clause
        ( expression-list )

initializer-clause:
    assignment-expression
    { initializer-list ,opt }
    { }

In your example, x is an assignment-expression which follows this chain of grammar productions:

conditional-expression  ==>
    logical-or-expression ==>
        logical-and-expression  ==>
            inclusive-or-expression  ==>
                exclusive-or-expression  ==>
                    and-expression  ==>
                        equality-expression  ==>
                            relational-expression  ==>
                                shift-expression  ==>
                                    additive-expression  ==>
                                        multiplicative-expression  ==>
                                            pm-expression  ==>
                                                cast-expression  ==>
                                                    unary-expression  ==>
                                                        postfix-expression  ==>
                                                            primary-expression  ==> 
                                                                id-expression  ==>
                                                                    unqualified-id  ==>
                                                                        identifier

And an identifier "is an lvalue if the entity is a function or variable" (5.1/4 "Primary expressions").

So in your example, the expression to the right of the = is an expression that happens to be an lvalue. It could be an rvalue of course, but it doesn't have to be. And there is no mandated lvalue-to-rvalue conversion.

I'm not sure what the value in knowing this is, though.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
1

3.10 Lvalues and rvalues

1 Every expression is either an lvalue or an rvalue.

2 An lvalue refers to an object or function. Some rvalue expressions—those of class or cvqualified class type—also refer to objects.47)

3 [Note: some builtin operators and function calls yield lvalues. [Example: if E is an expression of pointer type, then *E is an lvalue expression referring to the object or function to which E points. As another example, the function int& f(); yields an lvalue, so the call f() is an lvalue expression. ]

  1. [Note: some builin operators expect lvalue operands. [Example: builtin assignment operators all expect their left hand operands to be lvalues. ] Other builtin operators yield rvalues, and some expect them. [Example: the unary and binary + operators expect rvalue arguments and yield rvalue results. ] The discussion of each builtin operator in clause 5 indicates whether it expects lvalue operands and whether it yieldsan lvalue. ]

5 The result of calling a function that does not return a reference is an rvalue. User defined operators are functions, and whether such operators expect or yield lvalues is determined by their parameter and return types.

6 An expression which holds a temporary object resulting from a cast to a nonreference type is an rvalue (this includes the explicit creation of an object using functional notation (5.2.3)).

7 Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue; see 4.1, 4.2, and 4.3.

8 The discussion of reference initialization in 8.5.3 and of temporaries in 12.2 indicates the behavior of lvalues and rvalues in other significant contexts.

9 Class rvalues can have cvqualified types; nonclass rvalues always have cvunqualified types. Rvalues shall always have complete types or the void type; in addition to these types, lvalues can also have incomplete types.

10 An lvalue for an object is necessary in order to modify the object except that an rvalue of class type can also be used to modify its referent under certain circumstances. [Example: a member function called for an object (9.3) can modify the object. ]

11 Functions cannot be modified, but pointers to functions can be modifiable.

12 A pointer to an incomplete type can be modifiable. At some point in the program when the pointed to type is complete, the object at which the pointer points can also be modified.

13 The referent of a constqualified expression shall not be modified (through that expression), except that if it is of class type and has a mutable component, that component can be modified (7.1.5.1).

14 If an expression can be used to modify the object to which it refers, the expression is called modifiable. A program that attempts to modify an object through a nonmodifiable lvalue or rvalue expression is illformed.

15 If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined48): — the dynamic type of the object, — a cvqualified version of the dynamic type of the object, — a type that is the signed or unsigned type corresponding to the dynamic type of the object, — a type that is the signed or unsigned type corresponding to a cvqualified version of the dynamic type of the object, — an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), — a type that is a (possibly cvqualified) base class type of the dynamic type of the object, — a char or unsigned char type.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Kunal Vyas
  • 1,499
  • 1
  • 23
  • 40