c++11c++14decltypervaluexvalue

Is std::move(a).m an xvalue or a prvalue?


Let's say m is a non-static data member of non-reference type (T). According to cppreference, std::move(a).m is a prvalue until c++11. I guess it should be an xvalue after c++11. Please correct me if I'm wrong.

But the decltype(std::move(a).m) is still T (not T&&) in c++14 (visual studio, clang, gcc), which suggest std::move(a).m is still a prvalue. So is std::move(a).m an xvalue or a prvalue?


Solution

  • std::move(a).m is an xvalue.

    The new wordings make that much clearer, in [basic.lval]:

    • A prvalue is an expression whose evaluation initializes an object or a bit-field, or computes the value of the operand of an operator, as specified by the context in which it appears.
    • An xvalue is a glvalue that denotes an object or bit-field whose resources can be reused (usually because it is near the end of its lifetime).

    By those definitions, std::move(a).m is an xvalue and not a prvalue as it denotes an object.

    The way I find best to think about this is that glvalues have identity and rvalues are safe to move from - where lvalues have identity and are not safe to move from, xvalues have identity and are safe to move from, and prvalues do not have identity and are safe to move from. This taxonomy makes these kinds of questions easier to reason about.

    Additionally there is a note in [expr], which is more specific:

    [ Note: An expression is an xvalue if it is: [...]
    — a cast to an rvalue reference to object type,
    — a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or [...]
    —end note ]

    std::move(a) is a cast to rvalue reference, so is an xvalue. std::move(a).m is a class member access of an xvalue, so is an xvalue.


    As for decltype(std::move(a).m). Note that the word itself comes from declared type. The rules for what decltype(e) means are complicated, from [dcl.type.simple]:

    For an expression e, the type denoted by decltype(e) is defined as follows:
    — if e is an unparenthesized id-expression naming an lvalue or reference introduced from the identifier-list of a decomposition declaration, decltype(e) is the referenced type as given in the specification of the decomposition declaration (8.5);
    otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
    — otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
    — otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
    — otherwise, decltype(e) is the type of e.

    In this case, we have a class member access, so you just get the type of m - which is M and not M&&. On some level this makes sense, you're asking for the declared type of m and you got the declared type of m.

    If you want to categorize it properly, you can force that bullet to be ignored with an extra set of parentheses (obviously): decltype((std::move(a).m)) would give you M&&.