Skip to content

Commit

Permalink
[Clang] Implement P2308R1 - Template Parameter Initialization. (#73103)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cor3ntin committed Dec 1, 2023
1 parent f184147 commit f40d251
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 100 deletions.
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ C++2c Feature Support

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

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


Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -3933,6 +3933,11 @@ class Sema final {
APValue &Value, CCEKind CCE,
NamedDecl *Dest = nullptr);

ExprResult
EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value,
CCEKind CCE, bool RequireInt,
const APValue &PreNarrowingValue);

/// Abstract base class used to perform a contextual implicit
/// conversion from an expression to any type passing a filter.
class ContextualImplicitConverter {
Expand Down
11 changes: 8 additions & 3 deletions clang/lib/Parse/ParseTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1062,8 +1062,7 @@ Parser::ParseNonTypeTemplateParameter(unsigned Depth, unsigned Position) {
++CurTemplateDepthTracker;
EnterExpressionEvaluationContext ConstantEvaluated(
Actions, Sema::ExpressionEvaluationContext::ConstantEvaluated);
DefaultArg =
Actions.CorrectDelayedTyposInExpr(ParseAssignmentExpression());
DefaultArg = Actions.CorrectDelayedTyposInExpr(ParseInitializer());
if (DefaultArg.isInvalid())
SkipUntil(tok::comma, tok::greater, StopAtSemi | StopBeforeMatch);
}
Expand Down Expand Up @@ -1582,6 +1581,8 @@ ParsedTemplateArgument Parser::ParseTemplateTemplateArgument() {
/// constant-expression
/// type-id
/// id-expression
/// braced-init-list [C++26, DR]
///
ParsedTemplateArgument Parser::ParseTemplateArgument() {
// C++ [temp.arg]p2:
// In a template-argument, an ambiguity between a type-id and an
Expand Down Expand Up @@ -1619,8 +1620,12 @@ ParsedTemplateArgument Parser::ParseTemplateArgument() {
}

// Parse a non-type template argument.
ExprResult ExprArg;
SourceLocation Loc = Tok.getLocation();
ExprResult ExprArg = ParseConstantExpressionInExprEvalContext(MaybeTypeCast);
if (getLangOpts().CPlusPlus11 && Tok.is(tok::l_brace))
ExprArg = ParseBraceInitializer();
else
ExprArg = ParseConstantExpressionInExprEvalContext(MaybeTypeCast);
if (ExprArg.isInvalid() || !ExprArg.get()) {
return ParsedTemplateArgument();
}
Expand Down
115 changes: 58 additions & 57 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6123,61 +6123,6 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From,
return Result;
}

/// EvaluateConvertedConstantExpression - Evaluate an Expression
/// That is a converted constant expression
/// (which was built with BuildConvertedConstantExpression)
static ExprResult EvaluateConvertedConstantExpression(
Sema &S, Expr *E, QualType T, APValue &Value, Sema::CCEKind CCE,
bool RequireInt, const APValue &PreNarrowingValue) {
ExprResult Result = E;
// Check the expression is a constant expression.
SmallVector<PartialDiagnosticAt, 8> Notes;
Expr::EvalResult Eval;
Eval.Diag = &Notes;

ConstantExprKind Kind;
if (CCE == Sema::CCEK_TemplateArg && T->isRecordType())
Kind = ConstantExprKind::ClassTemplateArgument;
else if (CCE == Sema::CCEK_TemplateArg)
Kind = ConstantExprKind::NonClassTemplateArgument;
else
Kind = ConstantExprKind::Normal;

if (!E->EvaluateAsConstantExpr(Eval, S.Context, Kind) ||
(RequireInt && !Eval.Val.isInt())) {
// The expression can't be folded, so we can't keep it at this position in
// the AST.
Result = ExprError();
} else {
Value = Eval.Val;

if (Notes.empty()) {
// It's a constant expression.
Expr *E = ConstantExpr::Create(S.Context, Result.get(), Value);
if (!PreNarrowingValue.isAbsent())
Value = std::move(PreNarrowingValue);
return E;
}
}

// It's not a constant expression. Produce an appropriate diagnostic.
if (Notes.size() == 1 &&
Notes[0].second.getDiagID() == diag::note_invalid_subexpr_in_const_expr) {
S.Diag(Notes[0].first, diag::err_expr_not_cce) << CCE;
} else if (!Notes.empty() && Notes[0].second.getDiagID() ==
diag::note_constexpr_invalid_template_arg) {
Notes[0].second.setDiagID(diag::err_constexpr_invalid_template_arg);
for (unsigned I = 0; I < Notes.size(); ++I)
S.Diag(Notes[I].first, Notes[I].second);
} else {
S.Diag(E->getBeginLoc(), diag::err_expr_not_cce)
<< CCE << E->getSourceRange();
for (unsigned I = 0; I < Notes.size(); ++I)
S.Diag(Notes[I].first, Notes[I].second);
}
return ExprError();
}

/// CheckConvertedConstantExpression - Check that the expression From is a
/// converted constant expression of type T, perform the conversion and produce
/// the converted expression, per C++11 [expr.const]p3.
Expand All @@ -6194,8 +6139,8 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From,
Value = APValue();
return Result;
}
return EvaluateConvertedConstantExpression(S, Result.get(), T, Value, CCE,
RequireInt, PreNarrowingValue);
return S.EvaluateConvertedConstantExpression(Result.get(), T, Value, CCE,
RequireInt, PreNarrowingValue);
}

ExprResult Sema::BuildConvertedConstantExpression(Expr *From, QualType T,
Expand Down Expand Up @@ -6226,6 +6171,62 @@ ExprResult Sema::CheckConvertedConstantExpression(Expr *From, QualType T,
return R;
}

/// EvaluateConvertedConstantExpression - Evaluate an Expression
/// That is a converted constant expression
/// (which was built with BuildConvertedConstantExpression)
ExprResult
Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value,
Sema::CCEKind CCE, bool RequireInt,
const APValue &PreNarrowingValue) {

ExprResult Result = E;
// Check the expression is a constant expression.
SmallVector<PartialDiagnosticAt, 8> Notes;
Expr::EvalResult Eval;
Eval.Diag = &Notes;

ConstantExprKind Kind;
if (CCE == Sema::CCEK_TemplateArg && T->isRecordType())
Kind = ConstantExprKind::ClassTemplateArgument;
else if (CCE == Sema::CCEK_TemplateArg)
Kind = ConstantExprKind::NonClassTemplateArgument;
else
Kind = ConstantExprKind::Normal;

if (!E->EvaluateAsConstantExpr(Eval, Context, Kind) ||
(RequireInt && !Eval.Val.isInt())) {
// The expression can't be folded, so we can't keep it at this position in
// the AST.
Result = ExprError();
} else {
Value = Eval.Val;

if (Notes.empty()) {
// It's a constant expression.
Expr *E = ConstantExpr::Create(Context, Result.get(), Value);
if (!PreNarrowingValue.isAbsent())
Value = std::move(PreNarrowingValue);
return E;
}
}

// It's not a constant expression. Produce an appropriate diagnostic.
if (Notes.size() == 1 &&
Notes[0].second.getDiagID() == diag::note_invalid_subexpr_in_const_expr) {
Diag(Notes[0].first, diag::err_expr_not_cce) << CCE;
} else if (!Notes.empty() && Notes[0].second.getDiagID() ==
diag::note_constexpr_invalid_template_arg) {
Notes[0].second.setDiagID(diag::err_constexpr_invalid_template_arg);
for (unsigned I = 0; I < Notes.size(); ++I)
Diag(Notes[I].first, Notes[I].second);
} else {
Diag(E->getBeginLoc(), diag::err_expr_not_cce)
<< CCE << E->getSourceRange();
for (unsigned I = 0; I < Notes.size(); ++I)
Diag(Notes[I].first, Notes[I].second);
}
return ExprError();
}

/// dropPointerConversions - If the given standard conversion sequence
/// involves any pointer conversions, remove them. This may change
Expand Down
98 changes: 65 additions & 33 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7313,49 +7313,74 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
return E;
}

QualType CanonParamType = Context.getCanonicalType(ParamType);
// Avoid making a copy when initializing a template parameter of class type
// from a template parameter object of the same type. This is going beyond
// the standard, but is required for soundness: in
// template<A a> struct X { X *p; X<a> *q; };
// ... we need p and q to have the same type.
//
// Similarly, don't inject a call to a copy constructor when initializing
// from a template parameter of the same type.
Expr *InnerArg = Arg->IgnoreParenImpCasts();
if (ParamType->isRecordType() && isa<DeclRefExpr>(InnerArg) &&
Context.hasSameUnqualifiedType(ParamType, InnerArg->getType())) {
NamedDecl *ND = cast<DeclRefExpr>(InnerArg)->getDecl();
if (auto *TPO = dyn_cast<TemplateParamObjectDecl>(ND)) {

SugaredConverted = TemplateArgument(TPO, ParamType);
CanonicalConverted =
TemplateArgument(TPO->getCanonicalDecl(), CanonParamType);
return Arg;
}
if (isa<NonTypeTemplateParmDecl>(ND)) {
SugaredConverted = TemplateArgument(Arg);
CanonicalConverted =
Context.getCanonicalTemplateArgument(SugaredConverted);
return Arg;
}
}

// The initialization of the parameter from the argument is
// a constant-evaluated context.
EnterExpressionEvaluationContext ConstantEvaluated(
*this, Sema::ExpressionEvaluationContext::ConstantEvaluated);

if (getLangOpts().CPlusPlus17) {
QualType CanonParamType = Context.getCanonicalType(ParamType);

// Avoid making a copy when initializing a template parameter of class type
// from a template parameter object of the same type. This is going beyond
// the standard, but is required for soundness: in
// template<A a> struct X { X *p; X<a> *q; };
// ... we need p and q to have the same type.
//
// Similarly, don't inject a call to a copy constructor when initializing
// from a template parameter of the same type.
Expr *InnerArg = Arg->IgnoreParenImpCasts();
if (ParamType->isRecordType() && isa<DeclRefExpr>(InnerArg) &&
Context.hasSameUnqualifiedType(ParamType, InnerArg->getType())) {
NamedDecl *ND = cast<DeclRefExpr>(InnerArg)->getDecl();
if (auto *TPO = dyn_cast<TemplateParamObjectDecl>(ND)) {

SugaredConverted = TemplateArgument(TPO, ParamType);
CanonicalConverted =
TemplateArgument(TPO->getCanonicalDecl(), CanonParamType);
return Arg;
}
if (isa<NonTypeTemplateParmDecl>(ND)) {
SugaredConverted = TemplateArgument(Arg);
CanonicalConverted =
Context.getCanonicalTemplateArgument(SugaredConverted);
return Arg;
}
}
bool IsConvertedConstantExpression = true;
if (isa<InitListExpr>(Arg) || ParamType->isRecordType()) {
InitializationKind Kind = InitializationKind::CreateForInit(
Arg->getBeginLoc(), /*DirectInit=*/false, Arg);
Expr *Inits[1] = {Arg};
InitializedEntity Entity =
InitializedEntity::InitializeTemplateParameter(ParamType, Param);
InitializationSequence InitSeq(*this, Entity, Kind, Inits);
ExprResult Result = InitSeq.Perform(*this, Entity, Kind, Inits);
if (Result.isInvalid() || !Result.get())
return ExprError();
Result = ActOnConstantExpression(Result.get());
if (Result.isInvalid() || !Result.get())
return ExprError();
Arg = ActOnFinishFullExpr(Result.get(), Arg->getBeginLoc(),
/*DiscardedValue=*/false,
/*IsConstexpr=*/true, /*IsTemplateArgument=*/true)
.get();
IsConvertedConstantExpression = false;
}

if (getLangOpts().CPlusPlus17) {
// C++17 [temp.arg.nontype]p1:
// A template-argument for a non-type template parameter shall be
// a converted constant expression of the type of the template-parameter.
APValue Value;
ExprResult ArgResult = CheckConvertedConstantExpression(
Arg, ParamType, Value, CCEK_TemplateArg, Param);
if (ArgResult.isInvalid())
return ExprError();
ExprResult ArgResult;
if (IsConvertedConstantExpression) {
ArgResult = BuildConvertedConstantExpression(Arg, ParamType,
CCEK_TemplateArg, Param);
if (ArgResult.isInvalid())
return ExprError();
} else {
ArgResult = Arg;
}

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

APValue PreNarrowingValue;
ArgResult = EvaluateConvertedConstantExpression(
ArgResult.get(), ParamType, Value, CCEK_TemplateArg, /*RequireInt=*/
false, PreNarrowingValue);
if (ArgResult.isInvalid())
return ExprError();

// Convert the APValue to a TemplateArgument.
switch (Value.getKind()) {
case APValue::None:
Expand Down
9 changes: 9 additions & 0 deletions clang/test/CXX/drs/dr20xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ namespace dr2026 { // dr2026: 11
}
}

namespace dr2049 { // dr2049: 18 drafting
#if __cplusplus > 202002L
template <int* x = {}> struct X {};
X<> a;
X<nullptr> b;
static_assert(__is_same(decltype(a), decltype(b)));
#endif
}

namespace dr2061 { // dr2061: yes
#if __cplusplus >= 201103L
namespace A {
Expand Down
27 changes: 27 additions & 0 deletions clang/test/CXX/drs/dr24xx.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %clang_cc1 -std=c++20 %s -verify
// RUN: %clang_cc1 -std=c++23 %s -verify
// expected-no-diagnostics

namespace dr2450 { // dr2450: 18 drafting
#if __cplusplus > 202002L
struct S {int a;};
template <S s>
void f(){}

void test() {
f<{0}>();
f<{.a= 0}>();
}

#endif
}

namespace dr2459 { // dr2459: 18 drafting
#if __cplusplus > 202002L
struct A {
constexpr A(float) {}
};
template<A> struct X {};
X<1> x;
#endif
}
7 changes: 4 additions & 3 deletions clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ namespace ClassNTTP {
template<A a> constexpr int f() { return a.y; }
static_assert(f<A{1,2}>() == 2);

template<A a> int id;
template<A a> int id; // #ClassNTTP1
constexpr A a = {1, 2};
static_assert(&id<A{1,2}> == &id<a>);
static_assert(&id<A{1,3}> != &id<a>);

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

struct B {
constexpr B() {}
Expand All @@ -90,8 +91,8 @@ namespace ConvertedConstant {
constexpr A(float) {}
};
template <A> struct X {};
void f(X<1.0f>) {} // OK, user-defined conversion
void f(X<2>) {} // expected-error {{conversion from 'int' to 'A' is not allowed in a converted constant expression}}
void f(X<1.0f>) {}
void g(X<2>) {}
}

namespace CopyCounting {
Expand Down

0 comments on commit f40d251

Please sign in to comment.