📜 ⬆️ ⬇️

C ++ expression categories

Categories of expressions, such as lvalue and rvalue , relate more to the fundamental theoretical concepts of the C ++ language than to the practical aspects of its use. For this reason, many even experienced programmers are rather vaguely aware of what they mean. In this article I will try to explain as simply as possible the meaning of these terms, diluting theory with practical examples. Immediately make a reservation: the article does not pretend to the most complete and rigorous description of categories of expressions, for details I recommend to contact directly to the source: C ++ language standard.


The article will have quite a lot of English-language terms, due to the fact that some of them are difficult to translate into Russian, while others are translated in different sources in different ways. Therefore, I will often indicate English terms in italics .

A bit of history


The terms lvalue and rvalue appeared in the C language. It is worth noting that the confusion was inherent in the terminology initially, because they refer to expressions, and not to values. Historically, a lvalue is something that can be left ( left ) of an assignment operator, and rvalue is something that can only be right ( right ).


lvalue = rvalue; 

However, such a definition somewhat simplifies and distorts the essence. The C89 standard defined lvalue as an object locator , i.e. an object with an identifiable memory location. Accordingly, everything that did not fit this definition was included in the rvalue category.


Bjarn rushes to the rescue


In C ++, the terminology of categories of expressions has evolved quite strongly, especially after the adoption of C ++ Standard 11, where the concepts of rvalue links and move semantics were introduced. The history of the emergence of new terminology is interestingly described in the Straustrup article “New” Value Terminology .


The basis of a new, more rigorous terminology was formed by 2 properties:



Identical expressions are generalized under the term glvalue ( generalized values ), the expressions being moved are called rvalue . Combinations of these two properties defined 3 main categories of expressions:


Have identityDeprived of identity
Cannot be movedlvalue-
Can be movedxvalueprvalue

In fact, in the C ++ Standard 17, the concept of copy elision appeared - the formalization of situations when the compiler can and should avoid copying and moving objects. In this regard, the prvalue may not necessarily be moved. Details and examples of this can be found here . However, this does not affect the understanding of the general pattern of categories of expressions.


In the modern C ++ Standard, the category structure is given in the form of the following scheme:


image


Let us examine in general terms the properties of categories, as well as language expressions that fall into each of the categories. Immediately, I note that the following lists of expressions for each category cannot be considered complete, for more precise and detailed information, refer directly to the C ++ Standard.


glvalue


Glvalue expressions have the following properties:



rvalue


Expressions in the rvalue category have the following properties:



 class A { public: A() = default; A(const A&) { std::cout << "A::A(const A&)\n"; } A(A&&) { std::cout << "A::A(A&&)\n"; } }; ......... A a; A b(a); //  A(const A&) A c(std::move(a)); //  A(A&&) 

Technically, A && is an rvalue and can be used to initialize both the constant lvalue reference and the rvalue reference. But thanks to this property, there is no ambiguity; a variant of the constructor is chosen that accepts an rvalue link.

lvalue


Properties:



The lvalue category includes the following expressions:



 void func() {} ......... auto* func_ptr = &func; // :     auto& func_ref = func; // :     int&& rrn = int(123); auto* pn = &rrn; // :    auto& rn = rrn; // :  lvalue- 


A string literal differs from all other literals in C ++ by the fact that it is an lvalue (albeit immutable). For example, you can get his address:

 auto* p = &”Hello, world!”; //   ,    

prvalue


Properties:



The prvalue category includes the following expressions:



xvalue


Properties:



Examples of xvalue category expressions :



indeed, for the result of a call to std :: move (), you cannot get an address in memory or initialize a link to it, but at the same time, this expression can be polymorphic:

 struct XA { virtual void f() { std::cout << "XA::f()\n"; } }; struct XB : public XA { virtual void f() { std::cout << "XB::f()\n"; } }; XA&& xa = XB(); auto* p = &std::move(xa); //  auto& r = std::move(xa); //  std::move(xa).f(); //  “XB::f()” 


Some special cases


Operator comma


For the inline comma operator, the category of the expression always matches the category of the second operand.


 int n = 0; auto* pn = &(1, n); // lvalue auto& rn = (1, n); // lvalue 1, n = 2; // lvalue auto* pt = &(1, int(123)); // , rvalue auto& rt = (1, int(123)); // , rvalue 

Void type expressions


Calls to functions that return void , type-casting expressions to void , and throwing exceptions ( throw ) are considered to be expressions of the prvalue category, but they cannot be used to initialize references or as function arguments.


Ternary comparison operator


Definition of the category of a ? b : c a ? b : c is a non-trivial case, it all depends on the categories of the second and third arguments ( b and c ):



For the ternary operator, a number of rules are defined, according to which implicit conversions can be applied to the arguments b and c, but this is somewhat beyond the topic of the article, and I am interested in referring to the section Standard Conditional operator [expr.cond] .


 int n = 1; int v = (1 > 2) ? throw 1 : n; // lvalue, .. throw   void,    n ((1 < 2) ? n : v) = 2; //  lvalue,  ,   ((1 < 2) ? n : int(123)) = 2; //   , ..    prvalue 

References to fields and methods of classes and structures


For expressions of the form am and p->m (here we are talking about the built-in operator -> ), the following rules apply:



For pointers to class members ( a.*mp and p->*mp ), the rules are similar:



Bit fields


Bit fields are a convenient tool for low-level programming, however, their implementation falls somewhat out of the general structure of categories of expressions. For example, a reference to a bit field seems to be an lvalue , since it may be present on the left side of an assignment operator. At the same time, it is not possible to take the address of the bit field or initialize a non-constant link. You can initialize a constant reference to a bit field, but a temporary copy of the object will be created:


Bit-fields [class.bit]
If the initializer is used for a reference, it is subject to the link is not directly bound to the bit field directly.

 struct BF { int f:3; }; BF b; bf = 1; // OK auto* pb = &b.f; //  auto& rb = bf; //  

Instead of conclusion


As I mentioned in the introduction, this description does not claim to be complete, but merely gives a general idea of ​​the categories of expressions. This representation will allow a little better understanding of the Standard paragraphs and the compiler error messages.


')

Source: https://habr.com/ru/post/441742/


All Articles