On how to recognize Rvalue or Lvalue reference and if-it-has-a-name rule

前端 未结 3 1039
一生所求
一生所求 2020-11-30 05:07

I was reading Thomas Becker\'s article on rvalue reference and their use. In there he defines what he calls if-it-has-a-name rule:

Things tha

3条回答
  •  醉话见心
    2020-11-30 05:36

    Question 1: That rule is strictly referring to classifying expressions of rvalue reference type, not expressions in general. I almost agree with it in this context ('almost' because there's a bit more to it, see the quote below). The precise wording is in a note in the Standard [Clause 5 paragraph 7]:

    In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not.

    (emphases mine, for obvious reasons)


    Question 2: As you can see from the other answers and comments (some nice examples in there), there are issues with general, concise statements about the value category of an expression. Here's the way I think about it.

    We need to look at the problem from the other side: instead of trying to specify what expressions are lvalues, list the kinds that are rvalues; lvalues are everything else.

    First, a couple of definitions to keep things clear:

    • An object means a region of storage for data, not a function and not a reference (it's the definition in the Standard).
    • When I say an expression generates something, I mean it doesn't just name it or refer to it, but actually constructs and returns it as the result of a combination of operators, function calls (possibly constructor calls) or casts (possibly implicit casts).

    Now, based primarily on [3.10] (but also quite a few other places in the Standard), an expression is an rvalue if and only if it is one of the following:

    1. a value that is not associated with an object (like this, or literals like 7, not string ones);
    2. an expression that generates an object by value, a.k.a. a temporary object;
    3. an expression that generates an rvalue reference to an object;
    4. recursively, one of the following expressions using an rvalue:
      • x.y, where x is an rvalue and y is a non-static member object;
      • x.*y, where x is an rvalue and y is a pointer to a member object;
      • x[y], where either x or y is an rvalue of array type (using the built-in [] operator).

    That's it.

    Well, technically, the following special cases are also rvalues, but I don't think they're relevant in practice:

    1. a function call returning void, a cast to void, or a throw (obviously not lvalues, I'm not sure why I'd ever be interested in their value category in practice);
    2. one of obj.mf, ptr->mf, obj.*pmf, or ptr->*pmf (mf is a non-static member function, pmf is a pointer to member function); here we're talking strictly about these forms, not the function call expressions that can be built with them, and you really can't do anything with these but make a function call, which is a different expression altogether (to which we need to apply the rules above).

    And that's really it. Everything else is an lvalue. I find it easy enough to reason about expressions this way, as all categories above are easily recognizable. For example, it's easy to look at an expression, rule out the cases above, and decide it's an lvalue. Even for category 4, which has a longer description, the expressions are easily recognizable (I tried hard to make it a one-liner, but ultimately failed).

    Expressions involving operators can be lvalues or rvalues depending on the exact operator being used. Built-in operators specify what happens in each case, but user-defined operator functions can change the rules. When determining the value category of an expression, both the structure of the expression and the types involved matter.


    Notes:

    • Regarding category 1:
      • this in the example refers to this the pointer value, not *this.
      • String literals are lvalues because they're arrays of static storage duration, so they don't fit in category 1 (they're associated with objects).
    • Some examples related to categories 2 and 3:
      • Given the declaration int& f(int), the expression f(7) doesn't generate an object by value, so it doesn't fit in category 2; it does generate a reference, but it's not an rvalue reference, so category 3 doesn't apply either; the expression is an lvalue.
      • Given the declaration int&& f(int), the expression f(7) generates an rvalue reference; category 3 applies here, so the expression is an rvalue.
      • Given the declaration int f(int), the expression f(7) generates an object by value; category 2 applies here, the expression is an rvalue.
      • For casts, we can apply the same reasoning as for the three bullets above.
      • Given the declaration int&& a, using the expression a doesn't generate an rvalue reference; it just uses an identifier of reference type. Category 3 doesn't apply, the expression is an lvalue.
      • Lambda expressions generate closure objects by value - they are in category 2.
    • Some examples related to category 4:
      • x->y is translated to (*x).y. *x is an lvalue (it doesn't fit in any of the categories above). So, if y is a non-static member object, x->y is an lvalue (it doesn't fit in category 4 because of *x and it doesn't fit in 6 because that one only talks about member functions).
      • In x.y, if y is a static member, then category 4 doesn't apply. Such an expression is always an lvalue, even if x is an rvalue (6 doesn't apply either, because it talks about non-static member functions).
      • In x.y, if y is of type T& or T&&, then it's not a member object (remember, objects, not references, not functions), so category 4 doesn't apply. Such an expression is always an lvalue, even if x is an rvalue and even if y is an rvalue reference.
    • Category 4 used to be a bit different in C++11, but I believe this wording is correct for C++14. (If you insist to know, the result of subscripting into an rvalue array used to be an lvalue in C++11, but is an xvalue in C++14 - issue 1213.)
    • Further separating rvalues into xvalues and prvalues is relatively straightforward for C++14: categories 1, 2, 5 and 6 are prvalues, 3 and 4 are xvalues. Things were slightly different for C++11: category 4 was split between prvalues, xvalues and lvalues (changed as noted above, and also as part of the resolution of issue 616). This can be important, as it can affect the type you get back from decltype, for example.

    All references are to N4140, the last C++14 draft before publication.

    I first found the last two special rvalue cases here (everything's also in the Standard, of course, but harder to find). Note that not everything on that page is accurate for C++14. It also contains a very nice summary on the rationale behind the primary value categories (at the top).

提交回复
热议问题