diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 43ea6a3ffd6e6..8733bb93f5708 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -184,6 +184,9 @@ C++2c Feature Support - Implemented `P2864R2 Remove Deprecated Arithmetic Conversion on Enumerations From C++26 `_. +- Implemented `P2361R6 Template parameter initialization `_. + This change is applied as a DR in all language modes. + Resolutions to C++ Defect Reports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6de1a098e067a..8b2ed6f7cd8cd 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -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 { diff --git a/clang/lib/Parse/ParseTemplate.cpp b/clang/lib/Parse/ParseTemplate.cpp index f556d0e6d4f8b..64fe4d50bba27 100644 --- a/clang/lib/Parse/ParseTemplate.cpp +++ b/clang/lib/Parse/ParseTemplate.cpp @@ -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); } @@ -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 @@ -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(); } diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 3a3e9234469d3..5026e1d603e5e 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -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 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. @@ -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, @@ -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 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 diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 34d7b8c731e90..09bbf14d39af5 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -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 struct X { X *p; X *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(InnerArg) && + Context.hasSameUnqualifiedType(ParamType, InnerArg->getType())) { + NamedDecl *ND = cast(InnerArg)->getDecl(); + if (auto *TPO = dyn_cast(ND)) { + + SugaredConverted = TemplateArgument(TPO, ParamType); + CanonicalConverted = + TemplateArgument(TPO->getCanonicalDecl(), CanonParamType); + return Arg; + } + if (isa(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 struct X { X *p; X *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(InnerArg) && - Context.hasSameUnqualifiedType(ParamType, InnerArg->getType())) { - NamedDecl *ND = cast(InnerArg)->getDecl(); - if (auto *TPO = dyn_cast(ND)) { - - SugaredConverted = TemplateArgument(TPO, ParamType); - CanonicalConverted = - TemplateArgument(TPO->getCanonicalDecl(), CanonParamType); - return Arg; - } - if (isa(ND)) { - SugaredConverted = TemplateArgument(Arg); - CanonicalConverted = - Context.getCanonicalTemplateArgument(SugaredConverted); - return Arg; - } - } + bool IsConvertedConstantExpression = true; + if (isa(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. @@ -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: diff --git a/clang/test/CXX/drs/dr20xx.cpp b/clang/test/CXX/drs/dr20xx.cpp index dd60af14bb6b7..4f81b0b413d4b 100644 --- a/clang/test/CXX/drs/dr20xx.cpp +++ b/clang/test/CXX/drs/dr20xx.cpp @@ -61,6 +61,15 @@ namespace dr2026 { // dr2026: 11 } } +namespace dr2049 { // dr2049: 18 drafting +#if __cplusplus > 202002L +template struct X {}; +X<> a; +X b; +static_assert(__is_same(decltype(a), decltype(b))); +#endif +} + namespace dr2061 { // dr2061: yes #if __cplusplus >= 201103L namespace A { diff --git a/clang/test/CXX/drs/dr24xx.cpp b/clang/test/CXX/drs/dr24xx.cpp new file mode 100644 index 0000000000000..3fd8539be53d8 --- /dev/null +++ b/clang/test/CXX/drs/dr24xx.cpp @@ -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 +void f(){} + +void test() { +f<{0}>(); +f<{.a= 0}>(); +} + +#endif +} + +namespace dr2459 { // dr2459: 18 drafting +#if __cplusplus > 202002L +struct A { + constexpr A(float) {} +}; +template struct X {}; +X<1> x; +#endif +} diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp index 9f9aad604d5f1..792dc78464b2a 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp @@ -62,12 +62,13 @@ namespace ClassNTTP { template constexpr int f() { return a.y; } static_assert(f() == 2); - template int id; + template int id; // #ClassNTTP1 constexpr A a = {1, 2}; static_assert(&id == &id); static_assert(&id != &id); 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() {} @@ -90,8 +91,8 @@ namespace ConvertedConstant { constexpr A(float) {} }; template 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 { diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp new file mode 100644 index 0000000000000..9fb6b440b6b2a --- /dev/null +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp @@ -0,0 +1,104 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++20 -Wconversion -verify %s + +struct Test { + int a = 0; + int b = 42; +}; + +template +struct A { + static constexpr auto a = t.a; + static constexpr auto b = t.b; +}; + +template +struct Auto {}; + +template +struct Explicit{}; + +struct L {}; +struct M {}; + +struct Constructor { + Constructor(L) {}; // expected-note {{here}} + constexpr Constructor(M){}; +}; + +template < Test = {} > +struct DefaultParam1{}; + +template < Test = {1, 2} > +struct DefaultParam2{}; + +template < Test = {. b = 5} > +struct DefaultParam3{}; + +void test() { + static_assert(A<{}>::a == 0); + static_assert(A<{}>::b == 42); + static_assert(A<{.a = 3}>::a == 3); + static_assert(A<{.b = 4}>::b == 4); + + Auto<{0}> a; // expected-error {{cannot deduce type of initializer list}} + + int notconst = 0; // expected-note {{declared here}} + A<{notconst}> _; // expected-error {{non-type template argument is not a constant expression}} \ + // expected-note {{read of non-const variable 'notconst' is not allowed in a constant expression}} + + + Explicit err; // expected-error {{non-type template argument is not a constant expression}} \ + // expected-note {{non-constexpr constructor 'Constructor' cannot be used in a constant expression}} + Explicit ok; + + + DefaultParam1<> d1; + DefaultParam2<> d2; + DefaultParam3<> d3; +} + +template struct B { /* ... */ }; +template struct C { /* ... */ }; +C<{ 42 }> c1; // expected-warning {{braces around scalar initializer}} + +struct J1 { + J1 *self=this; +}; +B j1; // expected-error {{pointer to temporary object is not allowed in a template argument}} + +struct J2 { + J2 *self=this; + constexpr J2() {} + constexpr J2(const J2&) {} +}; +B j2; // expected-error {{pointer to temporary object is not allowed in a template argument}} + + +namespace GH58434 { + +template +void f(); + +void test() { + f<{42}>(); +} + +} + +namespace GH73666 { + +template +struct A { + T x[I]; +}; + +template< class T, class... U > +A( T, U... ) -> A; + +template void foo() { } + +void bar() { + foo<{1}>(); +} + +} diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html index 141b2aa515ad9..c9bfe4c6caa83 100755 --- a/clang/www/cxx_dr_status.html +++ b/clang/www/cxx_dr_status.html @@ -12101,7 +12101,7 @@

C++ defect report implementation status

2049 drafting List initializer in non-type template default argument - Not resolved + Clang 18 2050 @@ -14507,7 +14507,7 @@

C++ defect report implementation status

2450 drafting braced-init-list as a template-argument - Not resolved + Clang 18 2451 @@ -14561,7 +14561,7 @@

C++ defect report implementation status

2459 drafting Template parameter initialization - Not resolved + Clang 18 2460 diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 9fb6b0cda4da5..197726f3aa3ee 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -151,7 +151,7 @@

C++2c implementation status

Template parameter initialization P2308R1 (DR) - No + Clang 18 Pack Indexing