[clang] [Clang] Static and explicit object member functions with the same parameter-type-lists (PR #93430)
via cfe-commits
cfe-commits at lists.llvm.org
Sun May 26 16:24:22 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: cor3ntin (cor3ntin)
<details>
<summary>Changes</summary>
Implement wg21.link/P2797.
Because taking the address of an explicit object member function results in a function pointer, a call expression where the id-expression is an explicit object member is made to behave consistently with that model.
This change forces clang to perform overload resolution in the presence of an id-expression of the form `(&Foo::bar)(args...)`, which we previously failed to do consistently.
---
Patch is 20.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/93430.diff
12 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+3)
- (modified) clang/include/clang/AST/ExprCXX.h (+4)
- (modified) clang/include/clang/Sema/Overload.h (+4)
- (modified) clang/lib/Sema/SemaExpr.cpp (+24-3)
- (modified) clang/lib/Sema/SemaOverload.cpp (+64-10)
- (modified) clang/test/CXX/drs/cwg1xx.cpp (+3-5)
- (modified) clang/test/CXX/drs/cwg26xx.cpp (+26)
- (added) clang/test/CXX/drs/cwg2771.cpp (+18)
- (modified) clang/test/CodeGenCXX/cxx2b-deducing-this.cpp (+20)
- (modified) clang/test/SemaCXX/cxx2b-deducing-this.cpp (+27)
- (modified) clang/www/cxx_dr_status.html (+3-3)
- (modified) clang/www/cxx_status.html (+1-1)
``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 825e91876ffce..0a945a9989b0d 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -209,6 +209,9 @@ C++23 Feature Support
- Added a ``__reference_converts_from_temporary`` builtin, completing the necessary compiler support for
`P2255R2: Type trait to determine if a reference binds to a temporary <https://wg21.link/P2255R2>`_.
+- Implemented `P2797R0: Static and explicit object member functions with the same parameter-type-lists <https://wg21.link/P2797R0>`.
+ This completes the support for "deducing this".
+
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index dbf693611a7fa..557e9fd99c293 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -3027,6 +3027,7 @@ class OverloadExpr : public Expr {
struct FindResult {
OverloadExpr *Expression;
bool IsAddressOfOperand;
+ bool IsAddressOfOperandWithParen;
bool HasFormOfMemberPointer;
};
@@ -3039,6 +3040,7 @@ class OverloadExpr : public Expr {
assert(E->getType()->isSpecificBuiltinType(BuiltinType::Overload));
FindResult Result;
+ bool HasParen = isa<ParenExpr>(E);
E = E->IgnoreParens();
if (isa<UnaryOperator>(E)) {
@@ -3048,10 +3050,12 @@ class OverloadExpr : public Expr {
Result.HasFormOfMemberPointer = (E == Ovl && Ovl->getQualifier());
Result.IsAddressOfOperand = true;
+ Result.IsAddressOfOperandWithParen = HasParen;
Result.Expression = Ovl;
} else {
Result.HasFormOfMemberPointer = false;
Result.IsAddressOfOperand = false;
+ Result.IsAddressOfOperandWithParen = false;
Result.Expression = cast<OverloadExpr>(E);
}
diff --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h
index 76311b00d2fc5..64cdd6cdf043d 100644
--- a/clang/include/clang/Sema/Overload.h
+++ b/clang/include/clang/Sema/Overload.h
@@ -899,6 +899,8 @@ class Sema;
/// object argument.
bool IgnoreObjectArgument : 1;
+ bool TookAddressOfOverload : 1;
+
/// True if the candidate was found using ADL.
CallExpr::ADLCallKind IsADLCandidate : 1;
@@ -999,6 +1001,8 @@ class Sema;
/// Initialization of an object of class type by constructor,
/// using either a parenthesized or braced list of arguments.
CSK_InitByConstructor,
+
+ CSK_AddressOfOverloadSet,
};
/// Information about operator rewrites to consider when adding operator
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 410f80ae864a1..15496f3323b02 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -5813,6 +5813,24 @@ static TypoCorrection TryTypoCorrectionForCall(Sema &S, Expr *Fn,
return TypoCorrection();
}
+static bool isParenthetizedAndQualifiedAddressOfExpr(Expr *Fn) {
+ if (!isa<ParenExpr>(Fn))
+ return false;
+
+ Fn = Fn->IgnoreParens();
+ auto *UO = dyn_cast<UnaryOperator>(Fn);
+ if (!UO)
+ return false;
+ assert(cast<UnaryOperator>(Fn)->getOpcode() == UO_AddrOf);
+ if (auto *DRE = dyn_cast<DeclRefExpr>(UO->getSubExpr()->IgnoreParens())) {
+ return DRE->hasQualifier();
+ }
+ if (auto *OVL = dyn_cast<OverloadExpr>(UO->getSubExpr()->IgnoreParens())) {
+ return OVL->getQualifier();
+ }
+ return false;
+}
+
/// ConvertArgumentsForCall - Converts the arguments specified in
/// Args/NumArgs to the parameter types of the function FDecl with
/// function prototype Proto. Call is the call expression itself, and
@@ -5834,9 +5852,12 @@ Sema::ConvertArgumentsForCall(CallExpr *Call, Expr *Fn,
// C99 6.5.2.2p7 - the arguments are implicitly converted, as if by
// assignment, to the types of the corresponding parameter, ...
+
+ bool AddressOf = isParenthetizedAndQualifiedAddressOfExpr(Fn);
bool HasExplicitObjectParameter =
- FDecl && FDecl->hasCXXExplicitFunctionObjectParameter();
- unsigned ExplicitObjectParameterOffset = HasExplicitObjectParameter ? 1 : 0;
+ !AddressOf && FDecl && FDecl->hasCXXExplicitFunctionObjectParameter();
+ unsigned ExplicitObjectParameterOffset =
+ HasExplicitObjectParameter && !AddressOf ? 1 : 0;
unsigned NumParams = Proto->getNumParams();
bool Invalid = false;
unsigned MinArgs = FDecl ? FDecl->getMinRequiredArguments() : NumParams;
@@ -6546,7 +6567,7 @@ ExprResult Sema::BuildCallExpr(Scope *Scope, Expr *Fn, SourceLocation LParenLoc,
OverloadExpr::FindResult find = OverloadExpr::find(Fn);
// We aren't supposed to apply this logic if there's an '&' involved.
- if (!find.HasFormOfMemberPointer) {
+ if (!find.HasFormOfMemberPointer || find.IsAddressOfOperandWithParen) {
if (Expr::hasAnyTypeDependentArguments(ArgExprs))
return CallExpr::Create(Context, Fn, ArgExprs, Context.DependentTy,
VK_PRValue, RParenLoc, CurFPFeatureOverrides());
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 0c89fca8d38eb..17cccefa8e929 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6971,6 +6971,7 @@ void Sema::AddOverloadCandidate(
Candidate.IsSurrogate = false;
Candidate.IsADLCandidate = IsADLCandidate;
Candidate.IgnoreObjectArgument = false;
+ Candidate.TookAddressOfOverload = false;
Candidate.ExplicitCallArguments = Args.size();
// Explicit functions are not actually candidates at all if we're not
@@ -7545,10 +7546,19 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl,
CandidateSet.getRewriteInfo().getRewriteKind(Method, PO);
Candidate.IsSurrogate = false;
Candidate.IgnoreObjectArgument = false;
+ Candidate.TookAddressOfOverload =
+ CandidateSet.getKind() == OverloadCandidateSet::CSK_AddressOfOverloadSet;
Candidate.ExplicitCallArguments = Args.size();
- unsigned NumParams = Method->getNumExplicitParams();
- unsigned ExplicitOffset = Method->isExplicitObjectMemberFunction() ? 1 : 0;
+ bool IgnoreExplicitObject =
+ (Method->isExplicitObjectMemberFunction() &&
+ CandidateSet.getKind() ==
+ OverloadCandidateSet::CSK_AddressOfOverloadSet);
+ unsigned ExplicitOffset =
+ !IgnoreExplicitObject && Method->isExplicitObjectMemberFunction() ? 1 : 0;
+ unsigned NumParams = Method->getNumParams() - ExplicitOffset;
+ if (CandidateSet.getKind() == OverloadCandidateSet::CSK_AddressOfOverloadSet)
+ NumParams += int(Method->isImplicitObjectMemberFunction());
// (C++ 13.3.2p2): A candidate function having fewer than m
// parameters is viable only if it has an ellipsis in its parameter
@@ -7566,7 +7576,12 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl,
// (8.3.6). For the purposes of overload resolution, the
// parameter list is truncated on the right, so that there are
// exactly m parameters.
- unsigned MinRequiredArgs = Method->getMinRequiredExplicitArguments();
+ unsigned MinRequiredArgs = Method->getMinRequiredArguments() - ExplicitOffset;
+ if (CandidateSet.getKind() ==
+ OverloadCandidateSet::CSK_AddressOfOverloadSet &&
+ Method->isImplicitObjectMemberFunction())
+ MinRequiredArgs++;
+
if (Args.size() < MinRequiredArgs && !PartialOverloading) {
// Not enough arguments.
Candidate.Viable = false;
@@ -7636,7 +7651,16 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl,
// exist for each argument an implicit conversion sequence
// (13.3.3.1) that converts that argument to the corresponding
// parameter of F.
- QualType ParamType = Proto->getParamType(ArgIdx + ExplicitOffset);
+ QualType ParamType;
+ if (CandidateSet.getKind() ==
+ OverloadCandidateSet::CSK_AddressOfOverloadSet &&
+ Method->isImplicitObjectMemberFunction()) {
+ ParamType = ArgIdx == 0
+ ? Method->getFunctionObjectParameterReferenceType()
+ : Proto->getParamType(ArgIdx - 1);
+ } else {
+ ParamType = Proto->getParamType(ArgIdx + ExplicitOffset);
+ }
Candidate.Conversions[ConvIdx]
= TryCopyInitialization(*this, Args[ArgIdx], ParamType,
SuppressUserConversions,
@@ -7998,6 +8022,7 @@ void Sema::AddConversionCandidate(
Candidate.Function = Conversion;
Candidate.IsSurrogate = false;
Candidate.IgnoreObjectArgument = false;
+ Candidate.TookAddressOfOverload = false;
Candidate.FinalConversion.setAsIdentityConversion();
Candidate.FinalConversion.setFromType(ConvType);
Candidate.FinalConversion.setAllToTypes(ToType);
@@ -8200,6 +8225,7 @@ void Sema::AddTemplateConversionCandidate(
Candidate.FailureKind = ovl_fail_bad_deduction;
Candidate.IsSurrogate = false;
Candidate.IgnoreObjectArgument = false;
+ Candidate.TookAddressOfOverload = false;
Candidate.ExplicitCallArguments = 1;
Candidate.DeductionFailure = MakeDeductionFailureInfo(Context, Result,
Info);
@@ -8240,6 +8266,7 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion,
Candidate.Viable = true;
Candidate.IsSurrogate = true;
Candidate.IgnoreObjectArgument = false;
+ Candidate.TookAddressOfOverload = false;
Candidate.ExplicitCallArguments = Args.size();
// Determine the implicit conversion sequence for the implicit
@@ -8465,6 +8492,7 @@ void Sema::AddBuiltinCandidate(QualType *ParamTys, ArrayRef<Expr *> Args,
Candidate.Function = nullptr;
Candidate.IsSurrogate = false;
Candidate.IgnoreObjectArgument = false;
+ Candidate.TookAddressOfOverload = false;
std::copy(ParamTys, ParamTys + Args.size(), Candidate.BuiltinParamTypes);
// Determine the implicit conversion sequences for each of the
@@ -10929,6 +10957,12 @@ OverloadCandidateSet::BestViableFunction(Sema &S, SourceLocation Loc,
if (Best->Function && Best->Function->isDeleted())
return OR_Deleted;
+ if (auto *M = dyn_cast_or_null<CXXMethodDecl>(Best->Function);
+ Kind == CSK_AddressOfOverloadSet && M &&
+ M->isImplicitObjectMemberFunction()) {
+ return OR_No_Viable_Function;
+ }
+
if (!EquivalentCands.empty())
S.diagnoseEquivalentInternalLinkageDeclarations(Loc, Best->Function,
EquivalentCands);
@@ -11538,7 +11572,8 @@ static bool CheckArityMismatch(Sema &S, OverloadCandidate *Cand,
/// General arity mismatch diagnosis over a candidate in a candidate set.
static void DiagnoseArityMismatch(Sema &S, NamedDecl *Found, Decl *D,
- unsigned NumFormalArgs) {
+ unsigned NumFormalArgs,
+ bool IsAddressOf = false) {
assert(isa<FunctionDecl>(D) &&
"The templated declaration should at least be a function"
" when diagnosing bad template argument deduction due to too many"
@@ -11551,7 +11586,8 @@ static void DiagnoseArityMismatch(Sema &S, NamedDecl *Found, Decl *D,
unsigned MinParams = Fn->getMinRequiredExplicitArguments();
// at least / at most / exactly
- bool HasExplicitObjectParam = Fn->hasCXXExplicitFunctionObjectParameter();
+ bool HasExplicitObjectParam =
+ !IsAddressOf && Fn->hasCXXExplicitFunctionObjectParameter();
unsigned ParamCount = FnTy->getNumParams() - (HasExplicitObjectParam ? 1 : 0);
unsigned mode, modeCount;
if (NumFormalArgs < MinParams) {
@@ -11593,7 +11629,8 @@ static void DiagnoseArityMismatch(Sema &S, NamedDecl *Found, Decl *D,
static void DiagnoseArityMismatch(Sema &S, OverloadCandidate *Cand,
unsigned NumFormalArgs) {
if (!CheckArityMismatch(S, Cand, NumFormalArgs))
- DiagnoseArityMismatch(S, Cand->FoundDecl, Cand->Function, NumFormalArgs);
+ DiagnoseArityMismatch(S, Cand->FoundDecl, Cand->Function, NumFormalArgs,
+ Cand->TookAddressOfOverload);
}
static TemplateDecl *getDescribedTemplate(Decl *Templated) {
@@ -14076,6 +14113,21 @@ static ExprResult FinishOverloadedCallExpr(Sema &SemaRef, Scope *S, Expr *Fn,
}
case OR_No_Viable_Function: {
+ if (*Best != CandidateSet->end() &&
+ CandidateSet->getKind() ==
+ clang::OverloadCandidateSet::CSK_AddressOfOverloadSet) {
+ if (CXXMethodDecl *M =
+ dyn_cast_if_present<CXXMethodDecl>((*Best)->Function);
+ M && M->isImplicitObjectMemberFunction()) {
+ CandidateSet->NoteCandidates(
+ PartialDiagnosticAt(
+ Fn->getBeginLoc(),
+ SemaRef.PDiag(diag::err_member_call_without_object) << 0 << M),
+ SemaRef, OCD_AmbiguousCandidates, Args);
+ return ExprError();
+ }
+ }
+
// Try to recover by looking for viable functions which the user might
// have meant to call.
ExprResult Recovery = BuildRecoveryCallExpr(SemaRef, S, Fn, ULE, LParenLoc,
@@ -14167,8 +14219,10 @@ ExprResult Sema::BuildOverloadedCallExpr(Scope *S, Expr *Fn,
Expr *ExecConfig,
bool AllowTypoCorrection,
bool CalleesAddressIsTaken) {
- OverloadCandidateSet CandidateSet(Fn->getExprLoc(),
- OverloadCandidateSet::CSK_Normal);
+ OverloadCandidateSet CandidateSet(
+ Fn->getExprLoc(), CalleesAddressIsTaken
+ ? OverloadCandidateSet::CSK_AddressOfOverloadSet
+ : OverloadCandidateSet::CSK_Normal);
ExprResult result;
if (buildOverloadedCallSet(S, Fn, ULE, Args, LParenLoc, &CandidateSet,
@@ -16333,7 +16387,7 @@ ExprResult Sema::FixOverloadedFunctionReference(Expr *E, DeclAccessPair Found,
assert(UnOp->getOpcode() == UO_AddrOf &&
"Can only take the address of an overloaded function");
if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Fn)) {
- if (Method->isStatic()) {
+ if (!Method->isImplicitObjectMemberFunction()) {
// Do nothing: static member functions aren't any different
// from non-member functions.
} else {
diff --git a/clang/test/CXX/drs/cwg1xx.cpp b/clang/test/CXX/drs/cwg1xx.cpp
index a8f9b705a9866..21859fc6b1cbf 100644
--- a/clang/test/CXX/drs/cwg1xx.cpp
+++ b/clang/test/CXX/drs/cwg1xx.cpp
@@ -843,23 +843,21 @@ namespace cwg161 { // cwg161: 3.1
};
}
-namespace cwg162 { // cwg162: no
+namespace cwg162 { // cwg162: 19
struct A {
char &f(char);
static int &f(int);
void g() {
int &a = (&A::f)(0);
- // FIXME: expected-error at -1 {{reference to overloaded function could not be resolved; did you mean to call it?}}
char &b = (&A::f)('0');
- // expected-error at -1 {{reference to overloaded function could not be resolved; did you mean to call it?}}
+ // expected-error at -1 {{non-const lvalue reference to type 'char' cannot bind to a value of unrelated type 'int'}}
}
};
int &c = (&A::f)(0);
- // FIXME: expected-error at -1 {{reference to overloaded function could not be resolved; did you mean to call it?}}
char &d = (&A::f)('0');
- // expected-error at -1 {{reference to overloaded function could not be resolved; did you mean to call it?}}
+ // expected-error at -1 {{non-const lvalue reference to type 'char' cannot bind to a value of unrelated type 'int'}}
}
// cwg163: na
diff --git a/clang/test/CXX/drs/cwg26xx.cpp b/clang/test/CXX/drs/cwg26xx.cpp
index d3c5b5bb7b6b9..6c91377643fb9 100644
--- a/clang/test/CXX/drs/cwg26xx.cpp
+++ b/clang/test/CXX/drs/cwg26xx.cpp
@@ -240,3 +240,29 @@ void test() {
}
}
#endif
+
+
+#if __cplusplus >= 202302L
+namespace cwg2692 { // cwg2692: 19
+
+ struct A {
+ static void f(A); // #cwg2692-1
+ void f(this A); // #cwg2692-2
+
+ void g();
+ };
+
+ void A::g() {
+ (&A::f)(A()); // expected-error {{call to 'f' is ambiguous}}
+ // expected-note@#cwg2692-1 {{candidate}}
+ // expected-note@#cwg2692-2 {{candidate}}
+
+
+
+ (&A::f)(); // expected-error {{no matching function for call to 'f'}}
+ // expected-note@#cwg2692-1 {{candidate function not viable: requires 1 argument, but 0 were provided}}
+ // expected-note@#cwg2692-2 {{candidate function not viable: requires at most 1 argument, but 0 were provided}}
+ }
+
+}
+#endif
diff --git a/clang/test/CXX/drs/cwg2771.cpp b/clang/test/CXX/drs/cwg2771.cpp
new file mode 100644
index 0000000000000..e877e6fe4a5fe
--- /dev/null
+++ b/clang/test/CXX/drs/cwg2771.cpp
@@ -0,0 +1,18 @@
+// RUN: %clang_cc1 -std=c++23 %s -ast-dump | FileCheck --check-prefixes=CXX23 %s
+
+namespace cwg2771 { // cwg2771: 18
+
+struct A{
+ int a;
+ void cwg2771(){
+ int* r = &a;
+ }
+};
+// CXX23: CXXMethodDecl{{.+}}cwg2771
+// CXX23-NEXT: CompoundStmt
+// CXX23-NEXT: DeclStmt
+// CXX23-NEXT: VarDecl
+// CXX23-NEXT: UnaryOperator
+// CXX23-NEXT: MemberExpr
+// CXX23-NEXT: CXXThisExpr{{.+}}'cwg2771::A *'
+}
diff --git a/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp b/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp
index 649fe2afbf4e9..f9f9fbd7397f8 100644
--- a/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp
+++ b/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp
@@ -245,3 +245,23 @@ void f() {
d();
}
}
+
+
+namespace P2797 {
+struct C {
+ void c(this const C&); // #first
+ void c() &; // #second
+ static void c(int = 0); // #third
+
+ void d() {
+ (&C::c)(C{});
+ (&C::c)();
+ }
+};
+void test() {
+ (void)C{}.d();
+}
+// CHECK-LABEL: {{.*}} @_ZN5P27971C1dEv
+// CHECK: call void @_ZNH5P27971C1cERKS0_
+// CHECK: call void @_ZN5P27971C1cEi
+}
diff --git a/clang/test/SemaCXX/cxx2b-deducing-this.cpp b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
index cdb9d1324b974..86046497c2ef2 100644
--- a/clang/test/SemaCXX/cxx2b-deducing-this.cpp
+++ b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
@@ -893,3 +893,30 @@ void g() {
a * lval;
}
}
+
+namespace P2797 {
+struct C {
+ void c(this const C&); // #first
+ void c() &; // #second
+ static void c(int = 0); // #third
+
+ void d() {
+ c(); // expected-error {{call to member function 'c' is ambiguous}}
+ // expected-note@#first {{candidate function}}
+ // expected-note@#second {{candidate function}}
+ // expected-note@#third {{candidate function}}
+
+ (C::c)(); // expected-error {{call to member function 'c' is ambiguous}}
+ // expected-note@#first {{candidate function}}
+ // expected-note@#second {{candidate function}}
+ // expected-note@#third {{candidate function}}
+
+ (&(C::c))(); // expected-error {{cannot create a non-constant pointer to member function}}
+ (&C::c)(C{});
+ (&C::c)(*this); // expected-error {{call to non-static member function without an object argument}}
+ // expected-note@#second {{candidate function}}
+
+ (&C::c)();
+ }
+};
+}
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 4cce88fe0490f..7df6c0a05a487 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -1010,7 +1010,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/162.html">162</a></td>
<td>CD1</td>
<td>(<TT>&C::f)()</TT> with nonstatic members</td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 19</td>
</tr>
<tr id="163">
<td><a href="https://cplusplus.github.io/CWG/issues/163.html">163</a></td>
@@ -15960,7 +15960,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2692.html">2692</a></td>
<td>C++23</td>
<td>Static and explicit object member functions with the same parameter-type-lists</td>
- <td class="unknown" align="center">Unknown</td>
+ <td class="unreleased" align="center">Clang 19</td>
</tr>
<tr class="open" id="2693">
<td><a href="https://cplusplus.github.io/CWG/issues/2693.html">2693</a></td>
@@ -16435,7 +16435,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a ...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/93430
More information about the cfe-commits
mailing list