[clang] [Clang] Implement CWG 2282 (PR #215157)
Igor Kudrin via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 10 23:24:52 PDT 2026
https://github.com/igorkudrin updated https://github.com/llvm/llvm-project/pull/215157
>From c51c5059f189232037c6edc1b730fc99c6b6f847 Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Tue, 7 Jul 2026 21:30:53 -0700
Subject: [PATCH 1/2] [Clang] Implement CWG 2282
Link: https://wg21.link/cwg2282
For non-over-aligned types, overload resolution now falls back to aligned
allocation functions.
---
clang/docs/ReleaseNotes.md | 3 ++
clang/lib/Sema/SemaExprCXX.cpp | 31 +++++++++++++------
clang/test/CXX/drs/cwg22xx.cpp | 26 ++++++++++++++++
clang/test/CXX/drs/cwg5xx.cpp | 2 +-
.../test/CXX/expr/expr.unary/expr.new/p14.cpp | 8 ++---
clang/test/SemaCXX/new-delete.cpp | 2 +-
.../std-align-val-t-in-operator-new.cpp | 14 +++++++--
clang/www/cxx_dr_status.html | 2 +-
8 files changed, 70 insertions(+), 18 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index fc947d05fad83..197e8e79da85b 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -135,6 +135,9 @@ features cannot lower the translation-unit ABI level;
#### Resolutions to C++ Defect Reports
+- Clang now falls back to alignment-aware allocation functions for
+ non-overaligned types, implementing [CWG2282](https://wg21.link/cwg2282).
+
### C Language Changes
#### C2y Feature Support
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index a76146a8d914f..5709957b14439 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -2728,7 +2728,7 @@ bool Sema::CheckAllocatedType(QualType AllocType, SourceLocation Loc,
static void diagnoseNoViableFunctionForAllocationOverloadResolution(
Sema &S, const LookupResult &R, SourceRange Range, ArrayRef<Expr *> Args,
OverloadCandidateSet &Candidates, OverloadCandidateSet *AlignedCandidates,
- Expr *AlignArg, bool IncludedMSVCFallback) {
+ Expr *AlignArg, bool IncludedMSVCFallback, bool AlignedBeforeUnaligned) {
// If this is an allocation of the form 'new (p) X' for some object
// pointer p (or an expression that will decay to such a pointer),
// diagnose the reason for the error.
@@ -2784,10 +2784,13 @@ static void diagnoseNoViableFunctionForAllocationOverloadResolution(
S.Diag(R.getNameLoc(), diag::err_ovl_no_viable_function_in_call)
<< R.getLookupName() << Range;
- if (AlignedCandidates)
+ if (AlignedCandidates && AlignedBeforeUnaligned)
AlignedCandidates->NoteCandidates(S, AlignedArgs, AlignedCands, "",
R.getNameLoc());
Candidates.NoteCandidates(S, Args, Cands, "", R.getNameLoc());
+ if (AlignedCandidates && !AlignedBeforeUnaligned)
+ AlignedCandidates->NoteCandidates(S, AlignedArgs, AlignedCands, "",
+ R.getNameLoc());
if (IncludedMSVCFallback)
S.Diag(R.getNameLoc(), diag::note_ovl_ms_allocation_fallback_failed)
<< Range;
@@ -2896,6 +2899,7 @@ DiagnoseAllocationLookupFailure(Sema &SemaRef, const LookupResult &R,
ImplicitAllocationArguments *UnalignedArgumentList = nullptr;
ImplicitAllocationArguments *AlignedArgumentList = nullptr;
bool IncludedMSVCFallback = false;
+ bool AlignedBeforeUnaligned = true;
for (ImplicitAllocationArguments &AllocationArguments : ArgumentCandidates) {
if (AllocationArguments.IsMSVCCompatibilityFallback) {
IncludedMSVCFallback = true;
@@ -2903,10 +2907,12 @@ DiagnoseAllocationLookupFailure(Sema &SemaRef, const LookupResult &R,
}
if (AllocationArguments.PassTypeIdentity == TypeAwareAllocationMode::Yes)
continue;
- if (AllocationArguments.PassAlignment == AlignedAllocationMode::Yes)
+ if (AllocationArguments.PassAlignment == AlignedAllocationMode::Yes) {
AlignedArgumentList = &AllocationArguments;
- else
+ AlignedBeforeUnaligned = !UnalignedArgumentList;
+ } else {
UnalignedArgumentList = &AllocationArguments;
+ }
}
if (!UnalignedArgumentList)
return;
@@ -2939,7 +2945,7 @@ DiagnoseAllocationLookupFailure(Sema &SemaRef, const LookupResult &R,
diagnoseNoViableFunctionForAllocationOverloadResolution(
SemaRef, R, Range, UnalignedArgs, UnalignedCandidates,
AlignedCandidates ? &*AlignedCandidates : nullptr, AlignArg,
- IncludedMSVCFallback);
+ IncludedMSVCFallback, AlignedBeforeUnaligned);
}
Expr *Sema::tryGetTypeIdentityArgument(QualType Type, SourceLocation Loc) {
@@ -3045,13 +3051,20 @@ Sema::resolveAllocationArguments(LookupResult &R,
*this, /*TypeIdentityArg=*/nullptr, AllocationSizeExpr,
AllocationAlignmentExpr, /*IsMSVCCompatibilityFallback=*/false);
- // C++17 [expr.new]p13:
- // If no matching function is found and the allocated object type has
- // new-extended alignment, the alignment argument is removed from the
- // argument list, and overload resolution is performed again.
+ // C++20 [expr.new]p18:
+ // If no matching function is found then
+ // — if the allocated object type has new-extended alignment, the
+ // alignment argument is removed from the argument list;
+ // — otherwise, an argument that is the type’s alignment and has type
+ // std::align_val_t is added into the argument list immediately after
+ // the first argument;
+ // and then overload resolution is performed again.
if (IAP.PassAlignment == AlignedAllocationMode::Yes)
FoundArguments.push_back(AlignedArguments);
FoundArguments.push_back(UnalignedArguments);
+ if (IAP.PassAlignment == AlignedAllocationMode::No &&
+ AllocationAlignmentExpr && getLangOpts().AlignedAllocation)
+ FoundArguments.push_back(AlignedArguments);
// The MSVC global fallback path
if (getLangOpts().MSVCCompat &&
diff --git a/clang/test/CXX/drs/cwg22xx.cpp b/clang/test/CXX/drs/cwg22xx.cpp
index 6d51afcdda743..c04e81d8dee4e 100644
--- a/clang/test/CXX/drs/cwg22xx.cpp
+++ b/clang/test/CXX/drs/cwg22xx.cpp
@@ -6,6 +6,12 @@
// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx17
// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx17
+__extension__ typedef __SIZE_TYPE__ size_t;
+#if __cplusplus >= 201703L
+namespace std {
+ enum class align_val_t : size_t {};
+} // namespace std
+#endif
namespace cwg2211 { // cwg2211: 8
#if __cplusplus >= 201103L
@@ -196,6 +202,26 @@ void g() {
#endif
} // namespace cwg2277
+namespace cwg2282 { // cwg2282: 24
+#if __cplusplus >= 201703L
+struct A {
+ void *operator new(size_t, std::align_val_t) = delete; // #cwg2282-new-align
+ void *operator new(size_t, std::align_val_t, double) = delete; // #cwg2282-new-align-placement
+};
+
+void f() {
+ (void)new A;
+ // since-cxx17-error at -1 {{call to deleted function 'operator new'}}
+ // since-cxx17-note@#cwg2282-new-align {{candidate function has been explicitly deleted}}
+ // since-cxx17-note@#cwg2282-new-align-placement {{candidate function not viable: requires 3 arguments, but 2 were provided}}
+ (void)new (1.5) A;
+ // since-cxx17-error at -1 {{call to deleted function 'operator new'}}
+ // since-cxx17-note@#cwg2282-new-align-placement {{candidate function has been explicitly deleted}}
+ // since-cxx17-note@#cwg2282-new-align {{candidate function not viable: requires 2 arguments, but 3 were provided}}
+}
+#endif
+} // namespace cwg2282
+
namespace cwg2285 { // cwg2285: 4
// Note: Clang 4 implements this DR but it set a wrong value of `__cplusplus`
#if __cplusplus >= 201703L
diff --git a/clang/test/CXX/drs/cwg5xx.cpp b/clang/test/CXX/drs/cwg5xx.cpp
index 74e2464f60885..374ebf09baf34 100644
--- a/clang/test/CXX/drs/cwg5xx.cpp
+++ b/clang/test/CXX/drs/cwg5xx.cpp
@@ -679,8 +679,8 @@ namespace cwg553 {
// "is looked up in global scope", where it is not visible.
void *p = new (c) int;
// expected-error at -1 {{no matching function for call to 'operator new'}}
- // since-cxx17-note@#cwg5xx-global-operator-new-aligned {{candidate function not viable: no known conversion from 'cwg553_class' to 'std::align_val_t' for 2nd argument}}
// expected-note@#cwg5xx-global-operator-new {{candidate function not viable: requires 1 argument, but 2 were provided}}
+ // since-cxx17-note@#cwg5xx-global-operator-new-aligned {{candidate function not viable: requires 2 arguments, but 3 were provided}}
struct namespace_scope {
friend void *operator new(size_t, namespace_scope);
diff --git a/clang/test/CXX/expr/expr.unary/expr.new/p14.cpp b/clang/test/CXX/expr/expr.unary/expr.new/p14.cpp
index d0b24c8fe47b7..2f8404a83c00d 100644
--- a/clang/test/CXX/expr/expr.unary/expr.new/p14.cpp
+++ b/clang/test/CXX/expr/expr.unary/expr.new/p14.cpp
@@ -6,7 +6,7 @@ namespace std { enum class align_val_t : size_t {}; }
struct Arg {} arg;
// If the type is aligned, first try with an alignment argument and then
-// without. If not, never consider supplying an alignment.
+// without. If not, try in the reverse order.
template<unsigned Align, typename ...Ts>
struct alignas(Align) Unaligned {
@@ -19,11 +19,11 @@ auto *ubp = new (arg) Unaligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2, Arg>; // e
template<unsigned Align, typename ...Ts>
struct alignas(Align) Aligned {
- void *operator new(size_t, std::align_val_t, Ts...) = delete; // expected-note 2{{deleted}} expected-note 2{{not viable}}
+ void *operator new(size_t, std::align_val_t, Ts...) = delete; // expected-note 4{{deleted}}
};
-auto *aa = new Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__>; // expected-error {{no matching}}
+auto *aa = new Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__>; // expected-error {{deleted}}
auto *ab = new Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2>; // expected-error {{deleted}}
-auto *aap = new (arg) Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__, Arg>; // expected-error {{no matching}}
+auto *aap = new (arg) Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__, Arg>; // expected-error {{deleted}}
auto *abp = new (arg) Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2, Arg>; // expected-error {{deleted}}
// If both are available, we prefer the aligned version for an overaligned
diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp
index 2a2f91186871e..f8a1743521415 100644
--- a/clang/test/SemaCXX/new-delete.cpp
+++ b/clang/test/SemaCXX/new-delete.cpp
@@ -15,7 +15,7 @@
// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c -fexperimental-new-constant-interpreter -DNEW_INTERP
// FIXME Location is (frontend)
-// cxx17-note@*:* {{candidate function not viable: requires 2 arguments, but 3 were provided}}
+// cxx17-note@*:* {{candidate function not viable: requires 2 arguments, but 4 were provided}}
#include <stddef.h>
diff --git a/clang/test/SemaCXX/std-align-val-t-in-operator-new.cpp b/clang/test/SemaCXX/std-align-val-t-in-operator-new.cpp
index 9c34cb8e0d508..bb8333e38d1dc 100644
--- a/clang/test/SemaCXX/std-align-val-t-in-operator-new.cpp
+++ b/clang/test/SemaCXX/std-align-val-t-in-operator-new.cpp
@@ -62,9 +62,14 @@ void *operator new(std::size_t, std::align_val_t, X); // #3
// FIXME: Consider improving notes 1 and 3 here to say that these are aligned
// allocation functions and the type is not over-aligned.
X *p = new (123) X; // expected-error {{no matching function}}
+#if __cpp_aligned_new
+// expected-note@#1 {{requires 2 arguments, but 3 were provided}}
+// expected-note@#3 {{no known conversion from 'int' to 'X' for 3rd argument}}
+#else
// expected-note@#1 {{no known conversion from 'int' to 'std::align_val_t' for 2nd argument}}
-// expected-note@#2 {{no known conversion from 'int' to 'X' for 2nd argument}}
// expected-note@#3 {{requires 3 arguments}}
+#endif
+// expected-note@#2 {{no known conversion from 'int' to 'X' for 2nd argument}}
// expected-note@* {{requires 1 argument, but 2 were provided}} (builtin)
#ifdef __cpp_aligned_new
@@ -77,7 +82,12 @@ Y *q = new (123) Y; // expected-error {{no matching function}}
#endif
X *r = new (std::align_val_t(32), 123) X; // expected-error {{no matching function}}
+#ifdef __cpp_aligned_new
+// expected-note@#1 {{requires 2 arguments, but 4 were provided}}
+// expected-note@#3 {{requires 3 arguments, but 4 were provided}}
+#else
// expected-note@#1 {{requires 2 arguments, but 3 were provided}}
-// expected-note@#2 {{requires 2 arguments, but 3 were provided}}
// expected-note@#3 {{no known conversion from 'int' to 'X' for 3rd argument}}
+#endif
+// expected-note@#2 {{requires 2 arguments, but 3 were provided}}
// expected-note@* {{requires 1 argument, but 3 were provided}} (builtin)
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 4d29e6b4b32f4..a2a96f470d94a 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -15763,7 +15763,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td>[<a href="https://wg21.link/expr.new">expr.new</a>]</td>
<td>C++20</td>
<td>Consistency with mismatched aligned/non-over-aligned allocation/deallocation functions</td>
- <td class="unknown" align="center">Unknown</td>
+ <td class="unreleased" align="center">Clang 24</td>
</tr>
<tr id="2283">
<td><a href="https://cplusplus.github.io/CWG/issues/2283.html">2283</a></td>
>From b32d522cc625b8573b9887c9177b84f8052a4f37 Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Mon, 10 Aug 2026 23:24:13 -0700
Subject: [PATCH 2/2] fixup! add "&&
!getLangOpts().AlignedAllocationUnavailable"
---
clang/lib/Sema/SemaExprCXX.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 5709957b14439..93cc1c8192599 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -3063,7 +3063,8 @@ Sema::resolveAllocationArguments(LookupResult &R,
FoundArguments.push_back(AlignedArguments);
FoundArguments.push_back(UnalignedArguments);
if (IAP.PassAlignment == AlignedAllocationMode::No &&
- AllocationAlignmentExpr && getLangOpts().AlignedAllocation)
+ AllocationAlignmentExpr && getLangOpts().AlignedAllocation &&
+ !getLangOpts().AlignedAllocationUnavailable)
FoundArguments.push_back(AlignedArguments);
// The MSVC global fallback path
More information about the cfe-commits
mailing list