| Document #: | P4138R0 [Latest] [Status] |
| Date: | 2026-03-23 |
| Project: | Programming Language C++ |
| Audience: |
Core Working Group |
| Reply-to: |
Vlad Serebrennikov <serebrennikov.vladislav@gmail.com> |
While looking at [CWG3103], I got interested how we arrived at status quo, specifically how [basic.scope.scope]/3.1 came to be, and this is what I found. One of the conclusions I arrived at is that it doesn’t make much sense to overload member functions with explicit object parameter of non-reference type with member functions of any other kind of object parameter with the same type, ignoring references.
R0:
The intent to give member functions with no ref-qualifier special treatment can be tracked all the way to [N1821] (Extending Move Semantics To *this (Revision 2)), which introduced ref-qualifiers:
The
&and&&suffixes are allowed only on members that allow a cv-qualifier in the same location (e.g., not constructors!). Additionally, neither of these forms can be overloaded with the existing form (the latter retains its binding semantics, including the exception that allows rvalues to be bound).
I think this was caused by the fact that implicit object parameter of a member function with no qualifiers can bind even rvalues, despite being of non-const lvalue reference type ([over.match.funcs]/5), which, I think, means the identity conversion and leads to ambiguous calls even without this restriction.
This, and the obvious rule that object parameters of the same type correspond, shape the status quo of the wording.
The table below summarizes whether object parameters of two declarations of member functions correspond (❌) or don’t correspond (✅). Relevant wording is [basic.scope.scope]/3 and [over.match.funcs]/4.
no ref
|
&
|
&&
|
this D
|
this D&
|
this D&&
|
|
|---|---|---|---|---|---|---|
| no ref | ❌ | |||||
& |
❌ | ❌ | ||||
&& |
❌ | ✅ | ❌ | |||
this D |
❌ | ✅ | ✅ | ❌ | ||
this D& |
❌ | ❌ | ✅ | ✅ | ❌ | |
this D&& |
❌ | ✅ | ❌ | ✅ | ✅ | ❌ |
Implementations agree on 18 out of 21 cases (Compiler Explorer):
this D and
this D&
to correspond (but this might be a desirable direction as discussed in
Proposed changes section).However, when one of the declarations is named by a using-declaration, implementations agree only on 18 out of 36 cases, which includes 15 additional symmetrical cases left blank in the table (Compiler Explorer):
Note that in this latter case correspondence (❌) doesn’t translate to program being ill-formed per [namespace.udecl]/11.
The table below summarizes whether a call to an overload set is
well-formed depending on value category of the object argument, focusing
on
this D.
By itself
|
With
this D
|
|
|---|---|---|
| no ref | Both | Conflicting |
& |
Lvalues | Rvalues |
&& |
Rvalues | Lvalues |
this D |
Both | Conflicting |
this D& |
Lvalues | Rvalues |
this D&& |
Rvalues | Lvalues |
Note how adding a
this D
overload to an existing member function F flips the set of
well-formed calls to its complement, because
this D is
best viable function in all cases, rendering F obsolete.
All implementations agree, with an exception of
Clang, which consider
this D and
this D&
to be corresponding object parameters.
Explicit object parameters of non-reference types should correspond with any other object parameters that have the same type ignoring references, i.e. it should be ill-formed to overload member functions with such object parameter. This will render the second example in [CWG3103] ill-formed as requested.
Worth mentioning that Clang already rejects
this D and
this D&
overloads. Proposed change will make it conformant.
Table below reflects correspondence of object parameters with the proposed changes applied.
no ref
|
&
|
&&
|
this D
|
this D&
|
this D&&
|
|
|---|---|---|---|---|---|---|
| no ref | ❌ | |||||
& |
❌ | ❌ | ||||
&& |
❌ | ✅ | ❌ | |||
this D |
❌ | ❌ | ❌ | ❌ | ||
this D& |
❌ | ❌ | ✅ | ❌ | ❌ | |
this D&& |
❌ | ✅ | ❌ | ❌ | ✅ | ❌ |
Change 6.4.1 [basic.scope.scope] paragraph 3 as follows:
3 Two non-static member functions have corresponding object parameters if
- (3.1)
exactly one is an implicit object member function with noref-qualifierand the types of their object parameters ([dcl.fct]), after removing references, are the same, or- (3.1) the types of their object parameters ([dcl.fct]) are equivalent
., or- (3.2) if one of the functions is
the types of their object parameters, after removing references, are equivalent.
- (3.2.1) an implicit object member function with no
ref-qualifieror- (3.2.2) a member function with an explicit object parameter whose type is not reference type
Change 6.4.1 [basic.scope.scope] paragraph 3 as follows:
3 Two non-static member functions have corresponding object parameters if
- (3.1) exactly one is an implicit object member function with no
ref-qualifierand the types of their object parameters ([dcl.fct]), after removing references, are the same, or- (3.2) one is a member function with explicit object parameter whose type is not reference type, and the types of their object parameters, after removing references, are the same, or
- (3.3) the types of their object parameters are equivalent.
Add a new subclause to C.1 [diff.cpp23] for 6 [basic] with the following paragraph:
Affected subclause: [basic.scope.scope]
Change: Explicit object parameter of non-reference type corresponds with any other object parameter of the same type, ignoring references.
Rationale: If an overload set of member functions that differ only in their object parameter has a function with an explicit object parameter of non-reference type, other functions in the overload set cannot be selected by overload resolution.
Effect on original feature: A valid C++ 2023 program that declares such an overload set is ill-formed.
[ Example:— end example ]struct S { void f(this S); void f() &; // ill-formed; previously well-formed void g(this S); void g() &&; // ill-formed; previously well-formed void h(this S); void h(this S&); // ill-formed; previously well-formed void i(this S); void i(this S&&); // ill-formed; previously well-formed };