Question

Explicit object member function discrepancies between different compilers

I wrote the following program in c++23. Here I've overloaded member functions. But for all cases different compilers give different result as shown below in comment. As you can see I have three cases below. Demo.

struct C {
    void j(this const C);
    void j() const ;             //#1: Clang:Nope while gcc and msvc:Ok

    //    void f(this C );   
    //    void f(C);            //#2:  EDG: Nope while Clang,gcc and msvc:OK 

    // void k(this const C&);
    // void k() const ;         //#3:   EDG: OK while Clang, gcc and msvc:Nope
  
};

I want to know what is the standard conformant behavior for the three cases.

#1 is accepted by gcc and msvc but rejected by clang saying:


<source>:3:10: error: class member cannot be redeclared
    3 |     void j() const ;             //#1: Clang:Nope while gcc and msvc:Ok
      |          ^
<source>:2:10: note: previous declaration is here
    2 |     void j(this const C);
      |
 5  295  5
1 Jan 1970

Solution

 7

tldr; #1 and #2 are well-formed while #3 is ill-formed for the reasons explained below.

The behavior of the program can be understood using basic.scope.scope#3 and basic.scope.scope#4.4 and additionally dcl.fct#4 that states:

Two non-static member functions have corresponding object parameters if:

  • exactly one is an implicit object member function with no ref-qualifier and the types of their object parameters ([dcl.fct]), after removing top-level references, are the same, or
  • their object parameters have the same type.

Two function or function template declarations declare corresponding overloads if:

  • both declare functions with the same non-object-parameter-type-list,17 equivalent ([temp.over.link]) trailing requires-clauses (if any, except as specified in [temp.friend]), and, if both are non-static members, they have corresponding object parameters, or

The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own parameter-declaration ([dcl.decl]). After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T”. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list.

Now let's apply this to your code snippets one by one:

Case #1

Here we consider the overloads:

void j(this const C);
void j() const;

Here the first overload has a top-level const which is ignored/deleted as per dcl.fct#4.4. This means that it's explicit object parameter has type C(after removal of const). Next, the second overload has object parameter of type const C(after removal of reference). This means that neither of basic.scope.scope is satisfied.

Thus these two don't have corresponding object parameters.

Now as per basic.scope.scope#4.4 these two overloads are not corresponding overloads.

Thus #1 is well-formed.


Case #2

Here we consider the overloads:

void f(this C );   
void f(C); 

This is well-formed because even though the type of their object parameters after removing reference is same(C), their non-object-parameter-type-list are different. Thus, basic.scope.scope4.4 is violated which means in this case, these are not corresponding-overloads.

Thus, #2 is well-formed.


Case #3

Here we consider the overloads:

void k(this const C&);
void k() const

This is also ill-formed because exactly one of them is an implicit object member function with a corresponding object parameter of same type const C after removing the reference. And hence these are corresponding overloads.

Thus, #3 is also ill-formed.

2024-07-17
user12002570