3

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?

wanghan02
  • 1,227
  • 7
  • 14
  • *"I guess it should be an xvalue after c++11"* Why guess? The very same article explicitly states just that. – Igor Tandetnik Feb 03 '17 at 14:22
  • 2
    `decltype(std::move(a).m)` probably falls under "unparenthesized class member access expression" [special case](http://en.cppreference.com/w/cpp/language/decltype) – Igor Tandetnik Feb 03 '17 at 14:27
  • `expression_name` can tell you what your compiler thinks about this: http://stackoverflow.com/a/20721887/576911 My compiler agrees with Barry. – Howard Hinnant Feb 03 '17 at 15:49
  • 2
    The value category "prvalue" did not exist before C++11. There were just lvalues and rvalues, Also, `std::move` did not exist. So talking about the value category of `move(a).m` pre C++11 makes no sense. – Nicol Bolas Feb 03 '17 at 15:52

1 Answers1

3

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&&.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • So why is it that `T()` doesn't have identity but `T().e` does? – T.C. Feb 03 '17 at 15:31
  • @T.C. The name. `T()` doesn't have a name, `T().e` is `e`. – Barry Feb 03 '17 at 15:33
  • I'm not sure I buy that. Regardless, take `T` = `int[2]` and compare `T{}` and `T{}[1]`. – T.C. Feb 03 '17 at 15:35
  • 1
    @T.C. Same idea. `[1]` functions as a name. – Barry Feb 03 '17 at 15:38
  • 1
    Meh. I simply don't see how "`[1]` is a name" is any more easier to understand than the current description, esp. in a world where you don't actually move from prvalues. – T.C. Feb 03 '17 at 15:49
  • @T.C. YMMV. Like I said, this is just the way I find to best reason about it. – Barry Feb 03 '17 at 15:59
  • 1
    I find the C++11 Stroustrup model + "guaranteed elision" to be easier when I'm not dealing with standardese and corner cases. But when I have to work with the latter two (and hence use the standard description), I find mixing them in to be more trouble than its worth. – T.C. Feb 03 '17 at 16:05