Doc Number: X3J16/93-0134 WG21/N0341 Date: September 28, 1993 Project: Programming Language C++ Ref Doc: 93-0062/N0269 Reply to: Sam Kendall Sun Microsystems Laboratories, Inc. Sam.Kendall@East.Sun.COM Suggested Changes to Section 3.7, Lvalues Samuel C. Kendall This document is in two parts. The first part suggests editorial changes to the first paragraph of f3.7, with changes bars relative to 93-0062/N0269. This is a conservative set of changes. It is intended to reflect our recent increased understanding of what lvalues and non-lvalues are, in line with Tom Plum's N0306 = 93-0099. The second part discusses other problems with f3.7, along with some related issues. One point of notation: I use "f" (as in "f3.7") to indicate sections of the WP (working paper). Thanks to Scott Turner, Josee Lajoie, and David Cok for their very helpful feedback on a draft of part 1. Part 2 has benefitted from countless discussions and reflector messages. 1. Suggested Editorial Changes to First Paragraph of f3.7 This text is intended as input to the editor, not as literal copy to be inserted into the WP. 1.1 The Text 3.7 Lvalues and Non-Lvalues | Every expression is either an _lvalue_ or a non-lvalue. | An lvalue refers to an object or function. | Some non-lvalue expressions -- those of class or cv-qualified | class type -- also refer to objects. | Some operators yield lvalues. For example, if E is an expression of pointer type, then *E is an lvalue expression referring to the object to which E points. The discussion of each operator in f5 indicates whether it expects lvalue operands and whether it yields an lvalue. The discussion of reference initialization in f8.4.3 | indicates the behavior of lvalues and non-lvalues in | other significant contexts. | [The remainder of f3.7 is unchanged] 1.2 Discussion I have renamed the section "Lvalues and Non-Lvalues", since those are the terms defined. I've deleted "An _object_ is a region of storage" because that sentence appears in f3/2 also. (The forward reference from f3/2 to f3.7 should be deleted.) Actually, the term object is first defined in f1.3. I've split some sentences, and I've divided the first paragraph into two paragraphs. The original section strongly implied that non-lvalues did not refer to objects. But some do. So lvalues are left without any simple defining property. Instead, they are defined by how they are created, and how they can be used, and this information is in f5 and f8.4.3. The latter is a forward reference, so to speak, that I added. I've deleted an incorrect sentence about the origins of the term "lvalue". If it has to remain, it should be in a footnote; and it has to be qualified as follows to be correct: The name "lvalue" comes from the assignment expression E1 = E2 in which the left operand E1 (if it is of built-in type) | must be an lvalue expression, although not all lvalue | expressions can appear as the left operand of an assignment. | 2. Other Problems With f3.7 2.1 "Non-Lvalue" is awkward The term "non-lvalue" is awkward. Let's bite the bullet and adopt the term "rvalue", defining "rvalue" as "non-lvalue". Our documents would be more pleasant to read. 2.2 Paragraph 2 f3.7/2 (as numbered in N0269 = 93-0062) is very unclear. On the surface, the problems with this paragraph (other than a couple of typos) are: (A) In C++, non-lvalues as well as lvalues can have cv-qualified type. (B) "Context where an lvalue is not expected" and another implicit context, the word "usually" in footnote 5, are not defined elsewhere. In trying to address these surface problems, we get to some deeper problems, which the rest of section 2.2 addresses. 2.2.1 What the paragraph tries to do This paragraph tries to express the following three rules: (1) Lvalues of cv-qualified type can be used as operands of built-in operators in many contexts. In these contexts cv-qualifiers are stripped so that the usual arithmetic conversions (or whatever conversions govern that operator) can be applied. For example: const int ci; ... ci + 5 ...; (2) Initialization of objects doesn't care about cv-qualifiers on the initializer. (3) It is an error to extract the value from an lvalue of incomplete type. 2.2.2 Deeper problems with the paragraph A similar paragraph in the ISO C standard (in the section "Lvalues and Function Designators") expresses these three rules for that language. But C++ is quite a bit more complicated than C. Here are the problems the paragraph has fitting into our WP. (C) (Relevant to (1) and (2)) Because of function overloading, a given expression can appear in two or more contexts at the same time. One may demand an lvalue and the other not. For example: struct A { ... }; struct B : A { ... }; void f(const A&); void f(B); const B cb; f(cb); // Matches f(B) (D) (Relevant to (1) and (2)) Because of OPERATOR overloading, a built-in operator and a user-defined operator may "compete" for the same operands. Eg: struct A {}; struct B { operator A(); operator int(); }; B operator-(B); B b; -b; // ? The rules governing this expression -- governing any interaction between user-defined and built-in operators -- are simply missing from the WP. Clearly it should be ambiguous, but no rules say so. This problem actually has little to do with lvalues, but I include it here because it is related to the other problems. (E) (Relevant to (1) and (2)) Because of user-defined conversions (UDCs), many rules including those about lvalue/non-lvalue contexts must be applied recursively (to calls of user-defined conversion operators) in a way that is hard to talk about precisely. In the example that follows, the first two initializations are "normal", but the second two involve UDCs, requiring the context rules to be invoked recursively: const int ci; struct A { A(const int&); }; struct B { B(int); }; // Initializa- Context // Normal initializations tion of... expects... int i = ci; // object non-lvalue const int& ir = ci; // reference lvalue // Initializations with UDCs A a = ci; // object lvalue const B& br = ci; // reference non-lvalue These examples are reasonably simple. Involving other conversions can make things even trickier. (F) (Relevant to (2)) This point is made better in f8.4/3: Objects of type T can be initialized with objects [should be "expressions"] of type T independently of const and volatile modifiers on both the initialized variable and on the initializer. (G) (Relevant to (3)) It is nowhere defined where an lvalue's value is extracted. In C the extraction of an lvalue's value, and the stripping of cv-qualifiers, happen together. In C++, since there are non-lvalues of cv-qualified type, these two things are separate. 2.2.3 Solutions? The solution for (C), (D), and (G) must await a more precise specification of initialization, overloading, and built-in operators. The solution to (E) probably awaits the same more precise spec, but a clever wordsmith may be able to attack it independently. The solution for (F) is purely editorial. 2.3 Other Attributes The WP indicates that an expression has two attributes, type and lvalue status. But in order to be precise it is necessary to think of expressions as having a _set_ of types and lvalue statuses, and of having many other attributes besides. Please see my paper "Type Analysis of C++ Expressions Using Attributes" for more information. A draft of it was sent out as reflector message c++std-core-2634.