This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 115b. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-08-20


2902. Implicit this transformation outside of permitted contexts

Section: 7.5.5.1  [expr.prim.id.general]     Status: review     Submitter: Vincent X     Date: 2024-06-14

(From submission #553.)

Subclause 7.5.5.1 [expr.prim.id.general] paragraph 2 specifies:

If an id-expression E denotes a non-static non-type member of some class C at a point where the current class (7.5.3 [expr.prim.this]) is X and the id-expression is transformed into a class member access expression using (*this) as the object expression.

This rule transforms the following valid code (according to the example in 7.5.3 [expr.prim.this] paragraph 5) into invalid code:

  struct A {
   int x;
   int a[sizeof(x)];
   decltype(x) f();
  };

This is a regression introduced by P1787R6 (Declarations and where to find them).

Proposed resolution (reviewed by CWG 2024-06-14) [SUPERSEDED]:

Change in 7.5.5.1 [expr.prim.id.general] paragraph 2 as follows:

If an id-expression E denotes a non-static non-type member of some class C at a point where the current class (7.5.3 [expr.prim.this]) is X and the id-expression is transformed into a class member access expression using (*this) as the object expression.

Additional notes (June, 2024)

Unevaluated uses in static member functions are valid (and should not be transformed). However, the resolution above does not address the status quo which does perform the transformation.

Additional notes (July, 2024)

Per #573 and P0847R7 (Deducing this), the intent was that explicit object member functions have naming restrictions similar to other non-static member functions. For example,

  struct C {
    int f(this C);
  };

  using T = decltype(C::f); // error
  int g = C::f(42); // error

The status quo wording does not reflect that desired outcome.

Possible resolution:

  1. Change in 7.5.5.1 [expr.prim.id.general] paragraph 2 through 4 as follows:

    If an id-expression E denotes a non-static non-type member of some class C at a point where the current class (7.5.3 [expr.prim.this]) is X and , the following rules apply. E is said to refer to a local member of some class X if E appears at a point where X is the current class and C is X or a base class of X. E has the following associated cv-qualification relative to some class X:
    • If E appears in a function-definition, member-declarator, or declarator that declares a member function or member function template of X, at a point after the optional cv-qualifier-seq, the associated cv-qualification is the cv-qualifier-seq, if present, and otherwise empty.
    • Otherwise, if E appears in a member-declarator declaring a non-static data member of X, at a point within the optional default member initializer, the associated cv-qualification is empty.
    • Otherwise, E has no associated cv-qualification.

    E has the following meaning:

    • If E is the id-expression of a class member access, see 7.6.1.5 [expr.ref].
    • Otherwise, if E is a qualified-id that is the un-parenthesized operand of the unary & operator, see 7.6.2.2 [expr.unary.op].
    • Otherwise, if E denotes a non-static data member with declared type T and E is not potentially evaluated, the result is an lvalue of type cv T where cv is empty unless E refers to a local member of some class X, in which case cv is the associated cv-qualification relative to X (if any). [ Example:
        template<int n> class R { };
        struct S {
          int m;
          char g(int&);         // #1
          int g(const int&);    // #2
          auto f() const -> R<sizeof(g(m))>;     // OK, return type is R<sizeof(int)>
        };
        int j = sizeof(S::m + 42); // OK
      
      -- end example ]
    • Otherwise, if E is refers to a local member of some class X:
      • If E appears in an implicit object member function, is potentially evaluated, and has a (possibly empty) associated cv-qualification relative to X, E is transformed into a class member access expression using (*this) as the object expression. This transformation does not apply in the template definition context (13.8.3.2 [temp.dep.type]).
      • Otherwise, E shall not be potentially evaluated; the type and value category of the result is as specified in 7.5.5.2 [expr.prim.id.unqual] and 7.5.5.3 [expr.prim.id.qual].
    • Otherwise, if E denotes a member enumerator, the result is as specified in 7.5.5.2 [expr.prim.id.unqual] and 7.5.5.3 [expr.prim.id.qual].
    • Otherwise, the program is ill-formed.
    • E is potentially evaluated or C is X or a base class of X, and
    • E is not the id-expression of a class member access expression (7.6.1.5 [expr.ref]), and
    • if E is a qualified-id, E is not the un-parenthesized operand of the unary & operator (7.6.2.2 [expr.unary.op]),

    the id-expression is transformed into a class member access expression using (*this) as the object expression. [Note 2: If C is not X or a base class of X, the class member access expression is ill-formed. Also, if the id-expression occurs within a static or explicit object member function, the class member access is ill-formed. —end note] This transformation does not apply in the template definition context (13.8.3.2 [temp.dep.type]).

    If an id-expression E denotes a member M of an anonymous union (11.5.2 [class.union.anon]) U:

    • If U is a non-static data member, E refers to M as a member of the lookup context of the terminal name of E (after any implicit transformation to a class member access expression). [Example 1: o.x is interpreted as o.u.x, where u names the anonymous union member. —end example]
    • Otherwise, E is interpreted as a class member access (7.6.1.5 [expr.ref]) that designates the member subobject M of the anonymous union variable for U. [Note 3: Under this interpretation, E no longer denotes a non-static data member. —end note] [Example 2: N::x is interpreted as N::u.x, where u names the anonymous union variable. —end example]

    An id-expression that denotes a non-static data member or implicit object member function of a class can only be used:

    • as part of a class member access (after any implicit transformation (see above)) in which the object expression refers to the member's class or a class derived from that class, or
    • to form a pointer to member (7.6.2.2 [expr.unary.op]), or
    • if that id-expression denotes a non-static data member and it appears in an unevaluated operand. [Example 3:
        struct S { int m; };
        int i = sizeof(S::m);      // OK
        int j = sizeof(S::m + 42); // OK
      
      end example]

  2. Change in 7.6.1.5 [expr.ref] paragraph 5 as follow:

    Otherwise, the object expression shall be of class type. The class type shall be complete unless the class member access appears in the definition of that class. E2 shall denote a member of that class. [Note 3: The program is ill-formed if the result differs from that when the class is complete (6.5.2 [class.member.lookup]). —end note] [Note 4: 6.5.5 [basic.lookup.qual] describes how names are looked up after the . and -> operators. —end note]