[clang] f40d251 - [Clang] Implement P2308R1 - Template Parameter Initialization. (#73103)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Dec 1 08:44:30 PST 2023
Author: cor3ntin
Date: 2023-12-01T17:44:22+01:00
New Revision: f40d25151c25e257f3ebd2696e0bf133fe2a30ff
URL: https://github.com/llvm/llvm-project/commit/f40d25151c25e257f3ebd2696e0bf133fe2a30ff
DIFF: https://github.com/llvm/llvm-project/commit/f40d25151c25e257f3ebd2696e0bf133fe2a30ff.diff
LOG: [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
Added:
clang/test/CXX/drs/dr24xx.cpp
clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp
Modified:
clang/docs/ReleaseNotes.rst
clang/include/clang/Sema/Sema.h
clang/lib/Parse/ParseTemplate.cpp
clang/lib/Sema/SemaOverload.cpp
clang/lib/Sema/SemaTemplate.cpp
clang/test/CXX/drs/dr20xx.cpp
clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp
clang/www/cxx_dr_status.html
clang/www/cxx_status.html
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 43ea6a3ffd6e6c2..8733bb93f5708ae 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 <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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 6de1a098e067a38..8b2ed6f7cd8cd8e 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 f556d0e6d4f8b6e..64fe4d50bba27bf 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 3a3e9234469d393..5026e1d603e5ee2 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<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.
@@ -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<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
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 34d7b8c731e9076..09bbf14d39af5ad 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<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.
@@ -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 dd60af14bb6b71d..4f81b0b413d4bd7 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 <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 {
diff --git a/clang/test/CXX/drs/dr24xx.cpp b/clang/test/CXX/drs/dr24xx.cpp
new file mode 100644
index 000000000000000..3fd8539be53d810
--- /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 <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
+}
diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp
index 9f9aad604d5f119..792dc78464b2a87 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<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() {}
@@ -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 {
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 000000000000000..9fb6b440b6b2af9
--- /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 <Test t>
+struct A {
+ static constexpr auto a = t.a;
+ static constexpr auto b = t.b;
+};
+
+template <auto N>
+struct Auto {};
+
+template <typename T, T elem>
+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<Constructor, {L{}}> 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<Constructor, {M{}}> ok;
+
+
+ DefaultParam1<> d1;
+ DefaultParam2<> d2;
+ DefaultParam3<> d3;
+}
+
+template<auto n> struct B { /* ... */ };
+template<int i> struct C { /* ... */ };
+C<{ 42 }> c1; // expected-warning {{braces around scalar initializer}}
+
+struct J1 {
+ J1 *self=this;
+};
+B<J1{}> 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{}> j2; // expected-error {{pointer to temporary object is not allowed in a template argument}}
+
+
+namespace GH58434 {
+
+template<int>
+void f();
+
+void test() {
+ f<{42}>();
+}
+
+}
+
+namespace GH73666 {
+
+template<class T, int I>
+struct A {
+ T x[I];
+};
+
+template< class T, class... U >
+A( T, U... ) -> A<T, 1 + sizeof...(U)>;
+
+template<A a> void foo() { }
+
+void bar() {
+ foo<{1}>();
+}
+
+}
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 141b2aa515ad9ad..c9bfe4c6caa8353 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -12101,7 +12101,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2049.html">2049</a></td>
<td>drafting</td>
<td>List initializer in non-type template default argument</td>
- <td align="center">Not resolved</td>
+ <td class="unreleased" align="center">Clang 18</td>
</tr>
<tr id="2050">
<td><a href="https://cplusplus.github.io/CWG/issues/2050.html">2050</a></td>
@@ -14507,7 +14507,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2450.html">2450</a></td>
<td>drafting</td>
<td><I>braced-init-list</I> as a <I>template-argument</I></td>
- <td align="center">Not resolved</td>
+ <td class="unreleased" align="center">Clang 18</td>
</tr>
<tr id="2451">
<td><a href="https://cplusplus.github.io/CWG/issues/2451.html">2451</a></td>
@@ -14561,7 +14561,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2459.html">2459</a></td>
<td>drafting</td>
<td>Template parameter initialization</td>
- <td align="center">Not resolved</td>
+ <td class="unreleased" align="center">Clang 18</td>
</tr>
<tr id="2460">
<td><a href="https://cplusplus.github.io/CWG/issues/2460.html">2460</a></td>
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 9fb6b0cda4da50d..197726f3aa3eee5 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -151,7 +151,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Template parameter initialization</td>
<td><a href="https://wg21.link/P2308R1">P2308R1</a> (<a href="#dr">DR</a>)</td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 18</td>
</tr>
<tr>
<td>Pack Indexing</td>
More information about the cfe-commits
mailing list