Skip to content

Commit 6e78aef

Browse files
authored
[Clang] Implement P2747 constexpr placement new (#104586)
The implementation follows the resolution of CWG2922
1 parent 7c90081 commit 6e78aef

12 files changed

+203
-38
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,16 @@ C++2c Feature Support
118118

119119
- Implemented `P2893R3 Variadic Friends <https://wg21.link/P2893>`_
120120

121+
- Implemented `P2747R2 constexpr placement new <https://wg21.link/P2747R2>`_.
122+
121123
C++23 Feature Support
122124
^^^^^^^^^^^^^^^^^^^^^
123125
- Removed the restriction to literal types in constexpr functions in C++23 mode.
124126

125127
C++20 Feature Support
126128
^^^^^^^^^^^^^^^^^^^^^
127129

130+
128131
Resolutions to C++ Defect Reports
129132
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
130133

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,8 @@ def note_constexpr_new : Note<
333333
def note_constexpr_new_non_replaceable : Note<
334334
"call to %select{placement|class-specific}0 %1">;
335335
def note_constexpr_new_placement : Note<
336-
"this placement new expression is not yet supported in constant expressions">;
336+
"this placement new expression is not supported in constant expressions "
337+
"%select{|before C++2c}0">;
337338
def note_constexpr_placement_new_wrong_type : Note<
338339
"placement new would change type of storage from %0 to %1">;
339340
def note_constexpr_new_negative : Note<

clang/lib/AST/ExprConstant.cpp

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6691,7 +6691,9 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange,
66916691
if (Size && Size > Value.getArrayInitializedElts())
66926692
expandArray(Value, Value.getArraySize() - 1);
66936693

6694-
for (; Size != 0; --Size) {
6694+
// The size of the array might have been reduced by
6695+
// a placement new.
6696+
for (Size = Value.getArraySize(); Size != 0; --Size) {
66956697
APValue &Elem = Value.getArrayInitializedElt(Size - 1);
66966698
if (!HandleLValueArrayAdjustment(Info, &LocE, ElemLV, ElemT, -1) ||
66976699
!HandleDestructionImpl(Info, CallRange, ElemLV, Elem, ElemT))
@@ -10003,23 +10005,14 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
1000310005
return false;
1000410006

1000510007
FunctionDecl *OperatorNew = E->getOperatorNew();
10008+
QualType AllocType = E->getAllocatedType();
10009+
QualType TargetType = AllocType;
1000610010

1000710011
bool IsNothrow = false;
1000810012
bool IsPlacement = false;
10009-
if (OperatorNew->isReservedGlobalPlacementOperator() &&
10010-
Info.CurrentCall->isStdFunction() && !E->isArray()) {
10011-
// FIXME Support array placement new.
10012-
assert(E->getNumPlacementArgs() == 1);
10013-
if (!EvaluatePointer(E->getPlacementArg(0), Result, Info))
10014-
return false;
10015-
if (Result.Designator.Invalid)
10016-
return false;
10017-
IsPlacement = true;
10018-
} else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
10019-
Info.FFDiag(E, diag::note_constexpr_new_non_replaceable)
10020-
<< isa<CXXMethodDecl>(OperatorNew) << OperatorNew;
10021-
return false;
10022-
} else if (E->getNumPlacementArgs()) {
10013+
10014+
if (E->getNumPlacementArgs() == 1 &&
10015+
E->getPlacementArg(0)->getType()->isNothrowT()) {
1002310016
// The only new-placement list we support is of the form (std::nothrow).
1002410017
//
1002510018
// FIXME: There is no restriction on this, but it's not clear that any
@@ -10030,22 +10023,38 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
1003010023
// (which should presumably be valid only if N is a multiple of
1003110024
// alignof(int), and in any case can't be deallocated unless N is
1003210025
// alignof(X) and X has new-extended alignment).
10033-
if (E->getNumPlacementArgs() != 1 ||
10034-
!E->getPlacementArg(0)->getType()->isNothrowT())
10035-
return Error(E, diag::note_constexpr_new_placement);
10036-
1003710026
LValue Nothrow;
1003810027
if (!EvaluateLValue(E->getPlacementArg(0), Nothrow, Info))
1003910028
return false;
1004010029
IsNothrow = true;
10030+
} else if (OperatorNew->isReservedGlobalPlacementOperator()) {
10031+
if (Info.CurrentCall->isStdFunction() || Info.getLangOpts().CPlusPlus26) {
10032+
if (!EvaluatePointer(E->getPlacementArg(0), Result, Info))
10033+
return false;
10034+
if (Result.Designator.Invalid)
10035+
return false;
10036+
TargetType = E->getPlacementArg(0)->getType();
10037+
IsPlacement = true;
10038+
} else {
10039+
Info.FFDiag(E, diag::note_constexpr_new_placement)
10040+
<< /*C++26 feature*/ 1 << E->getSourceRange();
10041+
return false;
10042+
}
10043+
} else if (E->getNumPlacementArgs()) {
10044+
Info.FFDiag(E, diag::note_constexpr_new_placement)
10045+
<< /*Unsupported*/ 0 << E->getSourceRange();
10046+
return false;
10047+
} else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) {
10048+
Info.FFDiag(E, diag::note_constexpr_new_non_replaceable)
10049+
<< isa<CXXMethodDecl>(OperatorNew) << OperatorNew;
10050+
return false;
1004110051
}
1004210052

1004310053
const Expr *Init = E->getInitializer();
1004410054
const InitListExpr *ResizedArrayILE = nullptr;
1004510055
const CXXConstructExpr *ResizedArrayCCE = nullptr;
1004610056
bool ValueInit = false;
1004710057

10048-
QualType AllocType = E->getAllocatedType();
1004910058
if (std::optional<const Expr *> ArraySize = E->getArraySize()) {
1005010059
const Expr *Stripped = *ArraySize;
1005110060
for (; auto *ICE = dyn_cast<ImplicitCastExpr>(Stripped);
@@ -10139,9 +10148,17 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) {
1013910148
bool found(APValue &Subobj, QualType SubobjType) {
1014010149
// FIXME: Reject the cases where [basic.life]p8 would not permit the
1014110150
// old name of the object to be used to name the new object.
10142-
if (!Info.Ctx.hasSameUnqualifiedType(SubobjType, AllocType)) {
10143-
Info.FFDiag(E, diag::note_constexpr_placement_new_wrong_type) <<
10144-
SubobjType << AllocType;
10151+
unsigned SubobjectSize = 1;
10152+
unsigned AllocSize = 1;
10153+
if (auto *CAT = dyn_cast<ConstantArrayType>(AllocType))
10154+
AllocSize = CAT->getZExtSize();
10155+
if (auto *CAT = dyn_cast<ConstantArrayType>(SubobjType))
10156+
SubobjectSize = CAT->getZExtSize();
10157+
if (SubobjectSize < AllocSize ||
10158+
!Info.Ctx.hasSimilarType(Info.Ctx.getBaseElementType(SubobjType),
10159+
Info.Ctx.getBaseElementType(AllocType))) {
10160+
Info.FFDiag(E, diag::note_constexpr_placement_new_wrong_type)
10161+
<< SubobjType << AllocType;
1014510162
return false;
1014610163
}
1014710164
Value = &Subobj;

clang/lib/Frontend/InitPreprocessor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
660660
Builder.defineMacro("__cpp_unicode_literals", "200710L");
661661
Builder.defineMacro("__cpp_user_defined_literals", "200809L");
662662
Builder.defineMacro("__cpp_lambdas", "200907L");
663-
Builder.defineMacro("__cpp_constexpr", LangOpts.CPlusPlus26 ? "202306L"
663+
Builder.defineMacro("__cpp_constexpr", LangOpts.CPlusPlus26 ? "202406L"
664664
: LangOpts.CPlusPlus23 ? "202211L"
665665
: LangOpts.CPlusPlus20 ? "201907L"
666666
: LangOpts.CPlusPlus17 ? "201603L"

clang/test/AST/ByteCode/new-delete.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ namespace std {
245245
namespace PlacementNew {
246246
constexpr int foo() { // both-error {{never produces a constant expression}}
247247
char c[sizeof(int)];
248-
new (c) int{12}; // ref-note {{call to placement 'operator new'}} \
248+
new (c) int{12}; // ref-note {{this placement new expression is not supported in constant expressions before C++2c}} \
249249
// expected-note {{subexpression not valid in a constant expression}}
250250
return 0;
251251
}
@@ -309,7 +309,7 @@ namespace placement_new_delete {
309309
constexpr bool bad(int which) {
310310
switch (which) {
311311
case 0:
312-
delete new (placement_new_arg{}) int; // ref-note {{call to placement 'operator new'}} \
312+
delete new (placement_new_arg{}) int; // ref-note {{this placement new expression is not supported in constant expressions}} \
313313
// expected-note {{subexpression not valid in a constant expression}}
314314
break;
315315

@@ -328,7 +328,7 @@ namespace placement_new_delete {
328328
case 4:
329329
// FIXME: This technically follows the standard's rules, but it seems
330330
// unreasonable to expect implementations to support this.
331-
delete new (std::align_val_t{64}) Overaligned; // ref-note {{placement new expression is not yet supported}} \
331+
delete new (std::align_val_t{64}) Overaligned; // ref-note {{this placement new expression is not supported in constant expressions}} \
332332
// expected-note {{subexpression not valid in a constant expression}}
333333
break;
334334
}

clang/test/CXX/drs/cwg29xx.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,29 @@ struct S {
2323
friend class C<Ts>::Nested...; // expected-error {{friend declaration expands pack 'Ts' that is declared it its own template parameter list}}
2424
};
2525
} // namespace cwg2917
26+
27+
#if __cplusplus >= 202400L
28+
29+
namespace std {
30+
using size_t = decltype(sizeof(0));
31+
};
32+
void *operator new(std::size_t, void *p) { return p; }
33+
void* operator new[] (std::size_t, void* p) {return p;}
34+
35+
36+
namespace cwg2922 { // cwg2922: 20 open 2024-07-10
37+
union U { int a, b; };
38+
constexpr U nondeterministic(bool i) {
39+
if(i) {
40+
U u;
41+
new (&u) int();
42+
// expected-note@-1 {{placement new would change type of storage from 'U' to 'int'}}
43+
return u;
44+
}
45+
return {};
46+
}
47+
constexpr U _ = nondeterministic(true);
48+
// expected-error@-1 {{constexpr variable '_' must be initialized by a constant expression}} \
49+
// expected-note@-1 {{in call to 'nondeterministic(true)'}}
50+
}
51+
#endif

clang/test/Lexer/cxx-features.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@
317317
#error "wrong value for __cpp_lambdas"
318318
#endif
319319

320-
#if check(constexpr, 0, 200704, 201304, 201603, 201907, 202211, 202306)
320+
#if check(constexpr, 0, 200704, 201304, 201603, 201907, 202211, 202406L)
321321
#error "wrong value for __cpp_constexpr"
322322
#endif
323323

clang/test/SemaCXX/constant-expression-cxx2a.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -994,7 +994,7 @@ namespace placement_new_delete {
994994
constexpr bool bad(int which) {
995995
switch (which) {
996996
case 0:
997-
delete new (placement_new_arg{}) int; // expected-note {{call to placement 'operator new'}}
997+
delete new (placement_new_arg{}) int; // expected-note {{this placement new expression is not supported in constant expressions}}
998998
break;
999999

10001000
case 1:
@@ -1012,7 +1012,7 @@ namespace placement_new_delete {
10121012
case 4:
10131013
// FIXME: This technically follows the standard's rules, but it seems
10141014
// unreasonable to expect implementations to support this.
1015-
delete new (std::align_val_t{64}) Overaligned; // expected-note {{placement new expression is not yet supported}}
1015+
delete new (std::align_val_t{64}) Overaligned; // expected-note {{this placement new expression is not supported in constant expressions}}
10161016
break;
10171017
}
10181018

clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// RUN: %clang_cc1 -std=c++2a -verify %s -DNEW=__builtin_operator_new -DDELETE=__builtin_operator_delete
2-
// RUN: %clang_cc1 -std=c++2a -verify %s "-DNEW=operator new" "-DDELETE=operator delete"
3-
// RUN: %clang_cc1 -std=c++2a -verify %s "-DNEW=::operator new" "-DDELETE=::operator delete"
1+
// RUN: %clang_cc1 -std=c++2a -verify=expected,cxx20 %s -DNEW=__builtin_operator_new -DDELETE=__builtin_operator_delete
2+
// RUN: %clang_cc1 -std=c++2a -verify=expected,cxx20 %s "-DNEW=operator new" "-DDELETE=operator delete"
3+
// RUN: %clang_cc1 -std=c++2a -verify=expected,cxx20 %s "-DNEW=::operator new" "-DDELETE=::operator delete"
4+
// RUN: %clang_cc1 -std=c++2c -verify=expected,cxx26 %s "-DNEW=::operator new" "-DDELETE=::operator delete"
45

56
constexpr bool alloc_from_user_code() {
67
void *p = NEW(sizeof(int)); // expected-note {{cannot allocate untyped memory in a constant expression; use 'std::allocator<T>::allocate'}}
@@ -90,9 +91,10 @@ constexpr int no_deallocate_nonalloc = (std::allocator<int>().deallocate((int*)&
9091
// expected-note@-2 {{declared here}}
9192

9293
void *operator new(std::size_t, void *p) { return p; }
93-
constexpr bool no_placement_new_in_user_code() { // expected-error {{never produces a constant expression}}
94+
void* operator new[] (std::size_t, void* p) {return p;}
95+
constexpr bool no_placement_new_in_user_code() { // cxx20-error {{constexpr function never produces a constant expression}}
9496
int a;
95-
new (&a) int(42); // expected-note {{call to placement 'operator new'}}
97+
new (&a) int(42); // cxx20-note {{this placement new expression is not supported in constant expressions before C++2c}}
9698
return a == 42;
9799
}
98100

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// RUN: %clang_cc1 -std=c++2c -verify %s
2+
3+
4+
namespace std {
5+
using size_t = decltype(sizeof(0));
6+
}
7+
8+
void *operator new(std::size_t, void *p) { return p; }
9+
void* operator new[] (std::size_t, void* p) {return p;}
10+
11+
12+
consteval int ok() {
13+
int i;
14+
new (&i) int(0);
15+
new (&i) int[1]{1};
16+
new (static_cast<void*>(&i)) int(0);
17+
return 0;
18+
}
19+
20+
consteval int conversion() {
21+
int i;
22+
new (static_cast<void*>(&i)) float(0);
23+
// expected-note@-1 {{placement new would change type of storage from 'int' to 'float'}}
24+
return 0;
25+
}
26+
27+
consteval int indeterminate() {
28+
int * indeterminate;
29+
new (indeterminate) int(0);
30+
// expected-note@-1 {{read of uninitialized object is not allowed in a constant expression}}
31+
return 0;
32+
}
33+
34+
consteval int array1() {
35+
int i[2];
36+
new (&i) int[]{1,2};
37+
new (&i) int[]{1};
38+
new (&i) int(0);
39+
new (static_cast<void*>(&i)) int[]{1,2};
40+
new (static_cast<void*>(&i)) int[]{1};
41+
return 0;
42+
}
43+
44+
consteval int array2() {
45+
int i[1];
46+
new (&i) int[2];
47+
//expected-note@-1 {{placement new would change type of storage from 'int[1]' to 'int[2]'}}
48+
return 0;
49+
}
50+
51+
struct S{
52+
int* i;
53+
constexpr S() : i(new int(42)) {} // #no-deallocation
54+
constexpr ~S() {delete i;}
55+
};
56+
57+
consteval void alloc() {
58+
S* s = new S();
59+
s->~S();
60+
new (s) S();
61+
delete s;
62+
}
63+
64+
65+
consteval void alloc_err() {
66+
S* s = new S();
67+
new (s) S();
68+
delete s;
69+
}
70+
71+
72+
73+
int a = ok();
74+
int b = conversion(); // expected-error {{call to consteval function 'conversion' is not a constant expression}} \
75+
// expected-note {{in call to 'conversion()'}}
76+
int c = indeterminate(); // expected-error {{call to consteval function 'indeterminate' is not a constant expression}} \
77+
// expected-note {{in call to 'indeterminate()'}}
78+
int d = array1();
79+
int e = array2(); // expected-error {{call to consteval function 'array2' is not a constant expression}} \
80+
// expected-note {{in call to 'array2()'}}
81+
int alloc1 = (alloc(), 0);
82+
int alloc2 = (alloc_err(), 0); // expected-error {{call to consteval function 'alloc_err' is not a constant expression}}
83+
// expected-note@#no-deallocation {{allocation performed here was not deallocated}}
84+
85+
constexpr int *intptr() {
86+
return new int;
87+
}
88+
89+
constexpr bool yay() {
90+
int *ptr = new (intptr()) int(42);
91+
bool ret = *ptr == 42;
92+
delete ptr;
93+
return ret;
94+
}
95+
static_assert(yay());
96+
97+
constexpr bool blah() {
98+
int *ptr = new (intptr()) int[3]{ 1, 2, 3 }; // expected-note {{placement new would change type of storage from 'int' to 'int[3]'}}
99+
bool ret = ptr[0] == 1 && ptr[1] == 2 && ptr[2] == 3;
100+
delete [] ptr;
101+
return ret;
102+
}
103+
static_assert(blah()); // expected-error {{not an integral constant expression}} \
104+
// expected-note {{in call to 'blah()'}}
105+
106+
constexpr int *get_indeterminate() {
107+
int *evil;
108+
return evil; // expected-note {{read of uninitialized object is not allowed in a constant expression}}
109+
}
110+
111+
constexpr bool bleh() {
112+
int *ptr = new (get_indeterminate()) int; // expected-note {{in call to 'get_indeterminate()'}}
113+
return true;
114+
}
115+
static_assert(bleh()); // expected-error {{not an integral constant expression}} \
116+
// expected-note {{in call to 'bleh()'}}

clang/www/cxx_dr_status.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17348,7 +17348,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
1734817348
<td><a href="https://cplusplus.github.io/CWG/issues/2922.html">2922</a></td>
1734917349
<td>open</td>
1735017350
<td>constexpr placement-new is too permissive</td>
17351-
<td align="center">Not resolved</td>
17351+
<td title="Clang 20 implements 2024-07-10 resolution" align="center">Not Resolved*</td>
1735217352
</tr></table>
1735317353

1735417354
</div>

clang/www/cxx_status.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
208208
<tr>
209209
<td><tt>constexpr</tt> placement new</td>
210210
<td><a href="https://wg21.link/P2747R2">P2747R2</a></td>
211-
<td class="none" align="center">No</td>
211+
<td class="unreleased" align="center">Clang 20</td>
212212
</tr>
213213
<tr>
214214
<td>Deleting a Pointer to an Incomplete Type Should be Ill-formed</td>

0 commit comments

Comments
 (0)