Skip to content

Commit f40d251

Browse files
authored
[Clang] Implement P2308R1 - Template Parameter Initialization. (#73103)
https://wiki.edg.com/pub/Wg21kona2023/StrawPolls/p2308r1.html This implements P2308R1 as a DR and resolves CWG2459, CWG2450 and CWG2049. Fixes #73666 Fixes #58434 Fixes #41227 Fixes #49978 Fixes #36296
1 parent f184147 commit f40d251

File tree

11 files changed

+287
-100
lines changed

11 files changed

+287
-100
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ C++2c Feature Support
184184

185185
- Implemented `P2864R2 Remove Deprecated Arithmetic Conversion on Enumerations From C++26 <https://wg21.link/P2864R2>`_.
186186

187+
- Implemented `P2361R6 Template parameter initialization <https://wg21.link/P2308R1>`_.
188+
This change is applied as a DR in all language modes.
189+
187190

188191
Resolutions to C++ Defect Reports
189192
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3933,6 +3933,11 @@ class Sema final {
39333933
APValue &Value, CCEKind CCE,
39343934
NamedDecl *Dest = nullptr);
39353935

3936+
ExprResult
3937+
EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value,
3938+
CCEKind CCE, bool RequireInt,
3939+
const APValue &PreNarrowingValue);
3940+
39363941
/// Abstract base class used to perform a contextual implicit
39373942
/// conversion from an expression to any type passing a filter.
39383943
class ContextualImplicitConverter {

clang/lib/Parse/ParseTemplate.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,8 +1062,7 @@ Parser::ParseNonTypeTemplateParameter(unsigned Depth, unsigned Position) {
10621062
++CurTemplateDepthTracker;
10631063
EnterExpressionEvaluationContext ConstantEvaluated(
10641064
Actions, Sema::ExpressionEvaluationContext::ConstantEvaluated);
1065-
DefaultArg =
1066-
Actions.CorrectDelayedTyposInExpr(ParseAssignmentExpression());
1065+
DefaultArg = Actions.CorrectDelayedTyposInExpr(ParseInitializer());
10671066
if (DefaultArg.isInvalid())
10681067
SkipUntil(tok::comma, tok::greater, StopAtSemi | StopBeforeMatch);
10691068
}
@@ -1582,6 +1581,8 @@ ParsedTemplateArgument Parser::ParseTemplateTemplateArgument() {
15821581
/// constant-expression
15831582
/// type-id
15841583
/// id-expression
1584+
/// braced-init-list [C++26, DR]
1585+
///
15851586
ParsedTemplateArgument Parser::ParseTemplateArgument() {
15861587
// C++ [temp.arg]p2:
15871588
// In a template-argument, an ambiguity between a type-id and an
@@ -1619,8 +1620,12 @@ ParsedTemplateArgument Parser::ParseTemplateArgument() {
16191620
}
16201621

16211622
// Parse a non-type template argument.
1623+
ExprResult ExprArg;
16221624
SourceLocation Loc = Tok.getLocation();
1623-
ExprResult ExprArg = ParseConstantExpressionInExprEvalContext(MaybeTypeCast);
1625+
if (getLangOpts().CPlusPlus11 && Tok.is(tok::l_brace))
1626+
ExprArg = ParseBraceInitializer();
1627+
else
1628+
ExprArg = ParseConstantExpressionInExprEvalContext(MaybeTypeCast);
16241629
if (ExprArg.isInvalid() || !ExprArg.get()) {
16251630
return ParsedTemplateArgument();
16261631
}

clang/lib/Sema/SemaOverload.cpp

Lines changed: 58 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6123,61 +6123,6 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From,
61236123
return Result;
61246124
}
61256125

6126-
/// EvaluateConvertedConstantExpression - Evaluate an Expression
6127-
/// That is a converted constant expression
6128-
/// (which was built with BuildConvertedConstantExpression)
6129-
static ExprResult EvaluateConvertedConstantExpression(
6130-
Sema &S, Expr *E, QualType T, APValue &Value, Sema::CCEKind CCE,
6131-
bool RequireInt, const APValue &PreNarrowingValue) {
6132-
ExprResult Result = E;
6133-
// Check the expression is a constant expression.
6134-
SmallVector<PartialDiagnosticAt, 8> Notes;
6135-
Expr::EvalResult Eval;
6136-
Eval.Diag = &Notes;
6137-
6138-
ConstantExprKind Kind;
6139-
if (CCE == Sema::CCEK_TemplateArg && T->isRecordType())
6140-
Kind = ConstantExprKind::ClassTemplateArgument;
6141-
else if (CCE == Sema::CCEK_TemplateArg)
6142-
Kind = ConstantExprKind::NonClassTemplateArgument;
6143-
else
6144-
Kind = ConstantExprKind::Normal;
6145-
6146-
if (!E->EvaluateAsConstantExpr(Eval, S.Context, Kind) ||
6147-
(RequireInt && !Eval.Val.isInt())) {
6148-
// The expression can't be folded, so we can't keep it at this position in
6149-
// the AST.
6150-
Result = ExprError();
6151-
} else {
6152-
Value = Eval.Val;
6153-
6154-
if (Notes.empty()) {
6155-
// It's a constant expression.
6156-
Expr *E = ConstantExpr::Create(S.Context, Result.get(), Value);
6157-
if (!PreNarrowingValue.isAbsent())
6158-
Value = std::move(PreNarrowingValue);
6159-
return E;
6160-
}
6161-
}
6162-
6163-
// It's not a constant expression. Produce an appropriate diagnostic.
6164-
if (Notes.size() == 1 &&
6165-
Notes[0].second.getDiagID() == diag::note_invalid_subexpr_in_const_expr) {
6166-
S.Diag(Notes[0].first, diag::err_expr_not_cce) << CCE;
6167-
} else if (!Notes.empty() && Notes[0].second.getDiagID() ==
6168-
diag::note_constexpr_invalid_template_arg) {
6169-
Notes[0].second.setDiagID(diag::err_constexpr_invalid_template_arg);
6170-
for (unsigned I = 0; I < Notes.size(); ++I)
6171-
S.Diag(Notes[I].first, Notes[I].second);
6172-
} else {
6173-
S.Diag(E->getBeginLoc(), diag::err_expr_not_cce)
6174-
<< CCE << E->getSourceRange();
6175-
for (unsigned I = 0; I < Notes.size(); ++I)
6176-
S.Diag(Notes[I].first, Notes[I].second);
6177-
}
6178-
return ExprError();
6179-
}
6180-
61816126
/// CheckConvertedConstantExpression - Check that the expression From is a
61826127
/// converted constant expression of type T, perform the conversion and produce
61836128
/// the converted expression, per C++11 [expr.const]p3.
@@ -6194,8 +6139,8 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From,
61946139
Value = APValue();
61956140
return Result;
61966141
}
6197-
return EvaluateConvertedConstantExpression(S, Result.get(), T, Value, CCE,
6198-
RequireInt, PreNarrowingValue);
6142+
return S.EvaluateConvertedConstantExpression(Result.get(), T, Value, CCE,
6143+
RequireInt, PreNarrowingValue);
61996144
}
62006145

62016146
ExprResult Sema::BuildConvertedConstantExpression(Expr *From, QualType T,
@@ -6226,6 +6171,62 @@ ExprResult Sema::CheckConvertedConstantExpression(Expr *From, QualType T,
62266171
return R;
62276172
}
62286173

6174+
/// EvaluateConvertedConstantExpression - Evaluate an Expression
6175+
/// That is a converted constant expression
6176+
/// (which was built with BuildConvertedConstantExpression)
6177+
ExprResult
6178+
Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value,
6179+
Sema::CCEKind CCE, bool RequireInt,
6180+
const APValue &PreNarrowingValue) {
6181+
6182+
ExprResult Result = E;
6183+
// Check the expression is a constant expression.
6184+
SmallVector<PartialDiagnosticAt, 8> Notes;
6185+
Expr::EvalResult Eval;
6186+
Eval.Diag = &Notes;
6187+
6188+
ConstantExprKind Kind;
6189+
if (CCE == Sema::CCEK_TemplateArg && T->isRecordType())
6190+
Kind = ConstantExprKind::ClassTemplateArgument;
6191+
else if (CCE == Sema::CCEK_TemplateArg)
6192+
Kind = ConstantExprKind::NonClassTemplateArgument;
6193+
else
6194+
Kind = ConstantExprKind::Normal;
6195+
6196+
if (!E->EvaluateAsConstantExpr(Eval, Context, Kind) ||
6197+
(RequireInt && !Eval.Val.isInt())) {
6198+
// The expression can't be folded, so we can't keep it at this position in
6199+
// the AST.
6200+
Result = ExprError();
6201+
} else {
6202+
Value = Eval.Val;
6203+
6204+
if (Notes.empty()) {
6205+
// It's a constant expression.
6206+
Expr *E = ConstantExpr::Create(Context, Result.get(), Value);
6207+
if (!PreNarrowingValue.isAbsent())
6208+
Value = std::move(PreNarrowingValue);
6209+
return E;
6210+
}
6211+
}
6212+
6213+
// It's not a constant expression. Produce an appropriate diagnostic.
6214+
if (Notes.size() == 1 &&
6215+
Notes[0].second.getDiagID() == diag::note_invalid_subexpr_in_const_expr) {
6216+
Diag(Notes[0].first, diag::err_expr_not_cce) << CCE;
6217+
} else if (!Notes.empty() && Notes[0].second.getDiagID() ==
6218+
diag::note_constexpr_invalid_template_arg) {
6219+
Notes[0].second.setDiagID(diag::err_constexpr_invalid_template_arg);
6220+
for (unsigned I = 0; I < Notes.size(); ++I)
6221+
Diag(Notes[I].first, Notes[I].second);
6222+
} else {
6223+
Diag(E->getBeginLoc(), diag::err_expr_not_cce)
6224+
<< CCE << E->getSourceRange();
6225+
for (unsigned I = 0; I < Notes.size(); ++I)
6226+
Diag(Notes[I].first, Notes[I].second);
6227+
}
6228+
return ExprError();
6229+
}
62296230

62306231
/// dropPointerConversions - If the given standard conversion sequence
62316232
/// involves any pointer conversions, remove them. This may change

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7313,49 +7313,74 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
73137313
return E;
73147314
}
73157315

7316+
QualType CanonParamType = Context.getCanonicalType(ParamType);
7317+
// Avoid making a copy when initializing a template parameter of class type
7318+
// from a template parameter object of the same type. This is going beyond
7319+
// the standard, but is required for soundness: in
7320+
// template<A a> struct X { X *p; X<a> *q; };
7321+
// ... we need p and q to have the same type.
7322+
//
7323+
// Similarly, don't inject a call to a copy constructor when initializing
7324+
// from a template parameter of the same type.
7325+
Expr *InnerArg = Arg->IgnoreParenImpCasts();
7326+
if (ParamType->isRecordType() && isa<DeclRefExpr>(InnerArg) &&
7327+
Context.hasSameUnqualifiedType(ParamType, InnerArg->getType())) {
7328+
NamedDecl *ND = cast<DeclRefExpr>(InnerArg)->getDecl();
7329+
if (auto *TPO = dyn_cast<TemplateParamObjectDecl>(ND)) {
7330+
7331+
SugaredConverted = TemplateArgument(TPO, ParamType);
7332+
CanonicalConverted =
7333+
TemplateArgument(TPO->getCanonicalDecl(), CanonParamType);
7334+
return Arg;
7335+
}
7336+
if (isa<NonTypeTemplateParmDecl>(ND)) {
7337+
SugaredConverted = TemplateArgument(Arg);
7338+
CanonicalConverted =
7339+
Context.getCanonicalTemplateArgument(SugaredConverted);
7340+
return Arg;
7341+
}
7342+
}
7343+
73167344
// The initialization of the parameter from the argument is
73177345
// a constant-evaluated context.
73187346
EnterExpressionEvaluationContext ConstantEvaluated(
73197347
*this, Sema::ExpressionEvaluationContext::ConstantEvaluated);
73207348

7321-
if (getLangOpts().CPlusPlus17) {
7322-
QualType CanonParamType = Context.getCanonicalType(ParamType);
7323-
7324-
// Avoid making a copy when initializing a template parameter of class type
7325-
// from a template parameter object of the same type. This is going beyond
7326-
// the standard, but is required for soundness: in
7327-
// template<A a> struct X { X *p; X<a> *q; };
7328-
// ... we need p and q to have the same type.
7329-
//
7330-
// Similarly, don't inject a call to a copy constructor when initializing
7331-
// from a template parameter of the same type.
7332-
Expr *InnerArg = Arg->IgnoreParenImpCasts();
7333-
if (ParamType->isRecordType() && isa<DeclRefExpr>(InnerArg) &&
7334-
Context.hasSameUnqualifiedType(ParamType, InnerArg->getType())) {
7335-
NamedDecl *ND = cast<DeclRefExpr>(InnerArg)->getDecl();
7336-
if (auto *TPO = dyn_cast<TemplateParamObjectDecl>(ND)) {
7337-
7338-
SugaredConverted = TemplateArgument(TPO, ParamType);
7339-
CanonicalConverted =
7340-
TemplateArgument(TPO->getCanonicalDecl(), CanonParamType);
7341-
return Arg;
7342-
}
7343-
if (isa<NonTypeTemplateParmDecl>(ND)) {
7344-
SugaredConverted = TemplateArgument(Arg);
7345-
CanonicalConverted =
7346-
Context.getCanonicalTemplateArgument(SugaredConverted);
7347-
return Arg;
7348-
}
7349-
}
7349+
bool IsConvertedConstantExpression = true;
7350+
if (isa<InitListExpr>(Arg) || ParamType->isRecordType()) {
7351+
InitializationKind Kind = InitializationKind::CreateForInit(
7352+
Arg->getBeginLoc(), /*DirectInit=*/false, Arg);
7353+
Expr *Inits[1] = {Arg};
7354+
InitializedEntity Entity =
7355+
InitializedEntity::InitializeTemplateParameter(ParamType, Param);
7356+
InitializationSequence InitSeq(*this, Entity, Kind, Inits);
7357+
ExprResult Result = InitSeq.Perform(*this, Entity, Kind, Inits);
7358+
if (Result.isInvalid() || !Result.get())
7359+
return ExprError();
7360+
Result = ActOnConstantExpression(Result.get());
7361+
if (Result.isInvalid() || !Result.get())
7362+
return ExprError();
7363+
Arg = ActOnFinishFullExpr(Result.get(), Arg->getBeginLoc(),
7364+
/*DiscardedValue=*/false,
7365+
/*IsConstexpr=*/true, /*IsTemplateArgument=*/true)
7366+
.get();
7367+
IsConvertedConstantExpression = false;
7368+
}
73507369

7370+
if (getLangOpts().CPlusPlus17) {
73517371
// C++17 [temp.arg.nontype]p1:
73527372
// A template-argument for a non-type template parameter shall be
73537373
// a converted constant expression of the type of the template-parameter.
73547374
APValue Value;
7355-
ExprResult ArgResult = CheckConvertedConstantExpression(
7356-
Arg, ParamType, Value, CCEK_TemplateArg, Param);
7357-
if (ArgResult.isInvalid())
7358-
return ExprError();
7375+
ExprResult ArgResult;
7376+
if (IsConvertedConstantExpression) {
7377+
ArgResult = BuildConvertedConstantExpression(Arg, ParamType,
7378+
CCEK_TemplateArg, Param);
7379+
if (ArgResult.isInvalid())
7380+
return ExprError();
7381+
} else {
7382+
ArgResult = Arg;
7383+
}
73597384

73607385
// For a value-dependent argument, CheckConvertedConstantExpression is
73617386
// permitted (and expected) to be unable to determine a value.
@@ -7366,6 +7391,13 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
73667391
return ArgResult;
73677392
}
73687393

7394+
APValue PreNarrowingValue;
7395+
ArgResult = EvaluateConvertedConstantExpression(
7396+
ArgResult.get(), ParamType, Value, CCEK_TemplateArg, /*RequireInt=*/
7397+
false, PreNarrowingValue);
7398+
if (ArgResult.isInvalid())
7399+
return ExprError();
7400+
73697401
// Convert the APValue to a TemplateArgument.
73707402
switch (Value.getKind()) {
73717403
case APValue::None:

clang/test/CXX/drs/dr20xx.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ namespace dr2026 { // dr2026: 11
6161
}
6262
}
6363

64+
namespace dr2049 { // dr2049: 18 drafting
65+
#if __cplusplus > 202002L
66+
template <int* x = {}> struct X {};
67+
X<> a;
68+
X<nullptr> b;
69+
static_assert(__is_same(decltype(a), decltype(b)));
70+
#endif
71+
}
72+
6473
namespace dr2061 { // dr2061: yes
6574
#if __cplusplus >= 201103L
6675
namespace A {

clang/test/CXX/drs/dr24xx.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %clang_cc1 -std=c++20 %s -verify
2+
// RUN: %clang_cc1 -std=c++23 %s -verify
3+
// expected-no-diagnostics
4+
5+
namespace dr2450 { // dr2450: 18 drafting
6+
#if __cplusplus > 202002L
7+
struct S {int a;};
8+
template <S s>
9+
void f(){}
10+
11+
void test() {
12+
f<{0}>();
13+
f<{.a= 0}>();
14+
}
15+
16+
#endif
17+
}
18+
19+
namespace dr2459 { // dr2459: 18 drafting
20+
#if __cplusplus > 202002L
21+
struct A {
22+
constexpr A(float) {}
23+
};
24+
template<A> struct X {};
25+
X<1> x;
26+
#endif
27+
}

clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,13 @@ namespace ClassNTTP {
6262
template<A a> constexpr int f() { return a.y; }
6363
static_assert(f<A{1,2}>() == 2);
6464

65-
template<A a> int id;
65+
template<A a> int id; // #ClassNTTP1
6666
constexpr A a = {1, 2};
6767
static_assert(&id<A{1,2}> == &id<a>);
6868
static_assert(&id<A{1,3}> != &id<a>);
6969

7070
int k = id<1>; // expected-error {{no viable conversion from 'int' to 'A'}}
71+
// expected-note@#ClassNTTP1 {{passing argument to parameter 'a' here}}
7172

7273
struct B {
7374
constexpr B() {}
@@ -90,8 +91,8 @@ namespace ConvertedConstant {
9091
constexpr A(float) {}
9192
};
9293
template <A> struct X {};
93-
void f(X<1.0f>) {} // OK, user-defined conversion
94-
void f(X<2>) {} // expected-error {{conversion from 'int' to 'A' is not allowed in a converted constant expression}}
94+
void f(X<1.0f>) {}
95+
void g(X<2>) {}
9596
}
9697

9798
namespace CopyCounting {

0 commit comments

Comments
 (0)