cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2813 [expr.ref] Class member access currently requires glvalue, but it shouldn't #411

Closed brevzin closed 4 months ago

brevzin commented 1 year ago

Reference (section label): [expr.ref]

Issue description: [expr.ref]/2 currently starts:

For the first option (dot) the first expression shall be a glvalue.

This means that in X().f(), because X() is a prvalue, the temporary materialization conversion is applied ([basic.lval]/7). Up until C++23, this detail wasn't significant since for any non-static member function f(), f()'s object parameter would have had reference type, so there was no other real alternative.

But since C++23, with deducing this, we can now have non-static member functions that take their explicit object parameter by value. Which means that this example (from Johel Ernesto Guerrero Peña) is ill-formed:

struct X {
  X() = default;

  X(const X&) = delete;
  X(X&&) = delete;
  X& operator=(const X&) = delete;
  X& operator=(X&&) = delete;

  void f(this X self) { }
};

void f() {
  X{}.f();
}

X{}.f() currently forces a temporary materialization conversion, which means that the explicit object parameter self is initialized not from the prvalue X{} (which would work) but rather from an xvalue, which does not.

But taking a function pointer to &X::f and invoking it with X{} would of course be fine. The more convenient direct member syntax should also be fine, and indeed P0847 contained motivating examples of using by-value member functions that take advantage of guaranteed copied elision.

Suggested resolution:

Is the quoted sentence actually useful? For the existing non-static member function use-cases, because the object parameter already has reference type, the temporary materialization conversion will already be applied. It seems to simply force an undesired move construction in this case. The resolution might simply be:

For the first option (dot) the first expression shall be a glvalue. For the second option (arrow) the first expression shall be a prvalue having pointer 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).

brevzin commented 1 year ago

Actually probably need to move the "shall be a glvalue" wording to force temporary materialization conversion to some of the other cases in [expr.ref]/6. If we're accessing a data member or enumerator, need to do it. I'm not sure whether it's necessary for the overload resolution case for static member functions, so possibly in that sub-bullet as well?

jensmaurer commented 10 months ago

CWG2813