0

[expr.ref]/2:

For the first option (dot) the first expression shall be a glvalue having complete class type. For the second option (arrow) the first expression shall be a prvalue having pointer to complete class type. The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of [expr.ref] will address only the first option (dot).68 In either case, the id-expression shall name a member of the class or of one of its base classes. [ Note: Because the name of a class is inserted in its class scope (Clause [class]), the name of a class is also considered a nested member of that class.  — end note ] [ Note: [basic.lookup.classref] describes how names are looked up after the . and -> operators.  — end note ]

According to this paragraph, the lvalue-to-rvalue conversion is applied to p in the snippet below. But it is not applied to a. Why does the standard prescribe a glvalue for the first option (dot) and a prvalue for the second option (arrow)?

struct A{ void f() {} };
A a;
A* p = new A;
int main() {
    a.f();
    p->f();
}
Alexander
  • 2,581
  • 11
  • 17
  • 1
    ... why *should* it be? It's not clear what the point of your question is. – Nicol Bolas Mar 25 '17 at 14:24
  • I try to remember if I ever cared whether the expression left of `.` or `->` is an LValue or RValue. If neither is overloaded it must be an LValue (or would not produce an address which in turn is absolutly necessary to apply the "offset" of the right expression). The `->` may be overloaded. Due to the implementation of the overloaded `->` operator an RValue on left side may become sufficient. (However, I struggle to imagine a practical example - probably my lack of fantasy.) Operator `.` may never be overloaded (its forbidden by the standard). – Scheff's Cat Mar 25 '17 at 15:07
  • 1
    @Scheff: It's perfectly valid to apply `.` to rvalues. – Nicol Bolas Mar 25 '17 at 15:33
  • @NicolBolas Could you give an example? I try to imagine but I can't. – Scheff's Cat Mar 25 '17 at 15:34
  • 1
    @Scheff: `string("foo").c_str()`. That's legal, though the return value cannot be used outside of the overall statement that it appears in. – Nicol Bolas Mar 25 '17 at 15:45
  • @NicolBolas This is where my brain starts to skrew: I _believe_ a constructor (`string("foo")`) produces an LValue even if it is a temporary instance. To prove this, this should be possible: `std::cout << std::string().assign("foo") << std::endl;` (and it is: [ideone](http://ideone.com/Tpbgx3)) Or am I totally wrong? – Scheff's Cat Mar 25 '17 at 16:02
  • @NicolBolas Or this: `std::cout << (std::string() = "foo") << std::endl;` which remembers me the thumb rule "Everyhing is an LValue which might appear left of an assignment operator." – Scheff's Cat Mar 25 '17 at 16:09
  • @NicolBolas `... why should it be? It's not clear what the point of your question is` Let me rephrase the question to be more clear. Why does the Standard prescribes the first option to be a glvalue and the second option a prvalue? – Alexander Mar 25 '17 at 17:48
  • The operand of `->` is a prvalue because the operand of `*` is a prvalue. It seems like a mistake that the operand of `.` would be required to be a glvalue. – aschepler Mar 25 '17 at 19:19
  • 1
    @Scheff `string("foo")` and `std::string()` are prvalues. At least, they were in C++14. – aschepler Mar 25 '17 at 19:19
  • @aschepler Recognizing that I'm missing something I googled and found [Value categories](http://en.cppreference.com/w/cpp/language/value_category). (Especially, the [History](http://en.cppreference.com/w/cpp/language/value_category#History) section is enlighting - I was not completely wrong though I was quite not up to date.) – Scheff's Cat Mar 26 '17 at 07:45

1 Answers1

3

Bear in mind that prvalues can be converted to xvalues via a temporary materialization conversion [conv.rval]:

A prvalue of type T can be converted to an xvalue of type T. This conversion initializes a temporary object (15.2) of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, and produces an xvalue denoting the temporary object. T shall be a complete type. [Note: If T is a class type (or array thereof), it must have an accessible and non-deleted destructor; see 15.4. — end note] [Example:

struct X { int n; };
int k = X().n;
// OK, X() prvalue is converted to xvalue

end example]

Prior to the introduction of this new prvalue-to-glvalue conversion, C++14 did not have the restriction for the postfix-expression to be a glvalue.

On that note, C++11 was the first revision to feature user-definable, unconstrained rvalue-to-lvalue conversions by means of the (then-)new rvalue reference type: auto&& x = f(); makes the prvalue f() into an xvalue x.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Great answer! (+1) – Alexander Mar 26 '17 at 17:34
  • I'm having some difficulty interpreting this sentence in [conv.rval]: `This conversion initializes a temporary object (15.2) of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, ... .` Could you elaborate on this? – Alexander Mar 26 '17 at 18:14
  • @Alexander: As of C++17, we don't think of prvalues as objects anymore, but rather as "rules for making objects". To get an actual object, you need to create and initialise one, and that's what's happening during materialization. The text is trying to say that an object is created as the result of the materialization conversion. Does that help? – Kerrek SB Mar 26 '17 at 18:18
  • Definitely. Thanks for your attention. – Alexander Mar 26 '17 at 18:24
  • @KerrekSB Notwithstanding the fact that the OP accepted your answer, I still think that his question was not answered. AFAICT he is asking: `Why does the standard prescribe a glvalue for the first option (dot) and a prvalue for the second option (arrow)?` I know that the compiler can convert a glvalue to a prvalue by [\[expr\]/9](http://eel.is/c++draft/expr#9) and it also can convert a prvalue into an xvalue by [\[conv.rval\]](http://eel.is/c++draft/conv.rval). But that doesn't answer the OP's question? – Mao Jun 26 '17 at 13:11
  • @jab: Look at the evolution. The restriction for the dot form is the minimal change that was needed to make the new value categories work. We need to materialize *somewhere*, but saying that once is enough. – Kerrek SB Jun 26 '17 at 15:29
  • @KerrekSB I'm thinking in terms of the arrow form. Why can't the standard prescribe a glvalue as the postfix expression at the left of `->` operator? We would just change one conversion for another as follows: right now, with the standard prescribing an prvalue, assuming the snippet `struct A{ void f(); } a; A* p; p = &a; p->f();` the `p`, an lvalue, is subjected to an lvalue-to-rvalue conversion, according to [`[expr`]/9](http://eel.is/c++draft/expr#9). (to be continued ...) – Mao Jun 27 '17 at 17:53
  • @KerrekSB Now suppose we had the standard prescribing a glvalue, instead of a prvalue, for the postfix expression at the left of `->` operator. We could then say that expression `&a`, a prvalue, at the left of the operator `->`, as in the statement `(&a)->f();` would be subjected to a conversion to an xvalue according to [\[conv.rval\]/1](http://eel.is/c++draft/conv.rval#1). What's the difference between the two strategies? Why was the first selected, by the Standard? – Mao Jun 27 '17 at 17:55