[clang] [Clang][C++26] Implement P3865R1 - CTAD for type template template parameters (PR #191409)
Corentin Jabot via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 23 03:00:58 PDT 2026
https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/191409
>From e9978c9bafbf59905509613841fc363a54cae5e0 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Wed, 22 Apr 2026 16:55:07 +0800
Subject: [PATCH 1/4] Remove a workaround when building __is_deducible type
trait for CTAD guides (#62)
Also this refactors getTemplateInstantiationArgs a bit, to have it accept
Decl* instead of NamedDecl* to avoid unnecessary casts from callers.
---
clang/include/clang/Sema/Sema.h | 4 +--
clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 33 -------------------
clang/lib/Sema/SemaTemplateInstantiate.cpp | 16 ++++++---
clang/test/AST/ast-dump-ctad-alias.cpp | 4 +--
.../nested-implicit-deduction-guides.cpp | 2 +-
5 files changed, 17 insertions(+), 42 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 20f7d84cfc475..da3d855a62360 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -13586,7 +13586,7 @@ class Sema final : public SemaBase {
/// Retrieve the template argument list(s) that should be used to
/// instantiate the definition of the given declaration.
///
- /// \param ND the declaration for which we are computing template
+ /// \param D the declaration for which we are computing template
/// instantiation arguments.
///
/// \param DC In the event we don't HAVE a declaration yet, we instead provide
@@ -13618,7 +13618,7 @@ class Sema final : public SemaBase {
/// when encountering a specialized member function template, rather than
/// returning immediately.
MultiLevelTemplateArgumentList getTemplateInstantiationArgs(
- const NamedDecl *D, const DeclContext *DC = nullptr, bool Final = false,
+ const Decl *D, const DeclContext *DC = nullptr, bool Final = false,
std::optional<ArrayRef<TemplateArgument>> Innermost = std::nullopt,
bool RelativeToPrimary = false, const FunctionDecl *Pattern = nullptr,
bool ForConstraintInstantiation = false,
diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
index 11a671cb9ff8d..501d8426c7506 100644
--- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
+++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
@@ -991,39 +991,6 @@ Expr *buildIsDeducibleConstraint(Sema &SemaRef,
QualType ReturnType,
SmallVector<NamedDecl *> TemplateParams) {
ASTContext &Context = SemaRef.Context;
- // Constraint AST nodes must use uninstantiated depth.
- if (auto *PrimaryTemplate =
- AliasTemplate->getInstantiatedFromMemberTemplate();
- PrimaryTemplate && TemplateParams.size() > 0) {
- LocalInstantiationScope Scope(SemaRef);
-
- // Adjust the depth for TemplateParams.
- unsigned AdjustDepth = PrimaryTemplate->getTemplateDepth();
- SmallVector<TemplateArgument> TransformedTemplateArgs;
- for (auto *TP : TemplateParams) {
- // Rebuild any internal references to earlier parameters and reindex
- // as we go.
- MultiLevelTemplateArgumentList Args;
- Args.setKind(TemplateSubstitutionKind::Rewrite);
- Args.addOuterTemplateArguments(TransformedTemplateArgs);
- NamedDecl *NewParam = transformTemplateParameter(
- SemaRef, AliasTemplate->getDeclContext(), TP, Args,
- /*NewIndex=*/TransformedTemplateArgs.size(),
- getDepthAndIndex(TP).first + AdjustDepth);
-
- TemplateArgument NewTemplateArgument =
- Context.getInjectedTemplateArg(NewParam);
- TransformedTemplateArgs.push_back(NewTemplateArgument);
- }
- // Transformed the ReturnType to restore the uninstantiated depth.
- MultiLevelTemplateArgumentList Args;
- Args.setKind(TemplateSubstitutionKind::Rewrite);
- Args.addOuterTemplateArguments(TransformedTemplateArgs);
- ReturnType = SemaRef.SubstType(
- ReturnType, Args, AliasTemplate->getLocation(),
- Context.DeclarationNames.getCXXDeductionGuideName(AliasTemplate));
- }
-
SmallVector<TypeSourceInfo *> IsDeducibleTypeTraitArgs = {
Context.getTrivialTypeSourceInfo(
Context.getDeducedTemplateSpecializationType(
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 8dfe33f8684bd..29a0c52dfa28c 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -54,6 +54,7 @@ struct Response {
const Decl *NextDecl = nullptr;
bool IsDone = false;
bool ClearRelativeToPrimary = true;
+ bool EnableSkipForSpecialization = false;
static Response Done() {
Response R;
R.IsDone = true;
@@ -327,6 +328,11 @@ Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,
}
}
}
+ if (isa<CXXDeductionGuideDecl>(Function)) {
+ auto Response = Response::UseNextDecl(Function);
+ Response.EnableSkipForSpecialization = true;
+ return Response;
+ }
}
// If this is a friend or local declaration and it declares an entity at
// namespace scope, take arguments from its lexical parent
@@ -486,19 +492,19 @@ Response HandleGenericDeclContext(const Decl *CurDecl) {
} // namespace
MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
- const NamedDecl *ND, const DeclContext *DC, bool Final,
+ const Decl *CurDecl, const DeclContext *DC, bool Final,
std::optional<ArrayRef<TemplateArgument>> Innermost, bool RelativeToPrimary,
const FunctionDecl *Pattern, bool ForConstraintInstantiation,
bool SkipForSpecialization, bool ForDefaultArgumentSubstitution) {
- assert((ND || DC) && "Can't find arguments for a decl if one isn't provided");
+ assert((CurDecl || DC) &&
+ "Can't find arguments for a decl if one isn't provided");
// Accumulate the set of template argument lists in this structure.
MultiLevelTemplateArgumentList Result;
using namespace TemplateInstArgsHelpers;
- const Decl *CurDecl = ND;
if (Innermost) {
- Result.addOuterTemplateArguments(const_cast<NamedDecl *>(ND), *Innermost,
+ Result.addOuterTemplateArguments(const_cast<Decl *>(CurDecl), *Innermost,
Final);
// Populate placeholder template arguments for TemplateTemplateParmDecls.
// This is essential for the case e.g.
@@ -555,6 +561,8 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
return Result;
if (R.ClearRelativeToPrimary)
RelativeToPrimary = false;
+ if (R.EnableSkipForSpecialization)
+ SkipForSpecialization = true;
assert(R.NextDecl);
CurDecl = R.NextDecl;
}
diff --git a/clang/test/AST/ast-dump-ctad-alias.cpp b/clang/test/AST/ast-dump-ctad-alias.cpp
index 9a3adbcb534e8..78353b4fdcf17 100644
--- a/clang/test/AST/ast-dump-ctad-alias.cpp
+++ b/clang/test/AST/ast-dump-ctad-alias.cpp
@@ -24,7 +24,7 @@ Out2<double>::AInner t(1.0);
// Verify that the require-clause of alias deduction guide is transformed correctly:
// - Occurrence T should be replaced with `int`;
// - Occurrence V should be replaced with the Y with depth 1
-// - Depth of occurrence Y in the __is_deducible constraint should be 1
+// - Depth of occurrence Y in the __is_deducible constraint should be 0
//
// CHECK: | `-FunctionTemplateDecl {{.*}} <deduction guide for AInner>
// CHECK-NEXT: | |-TemplateTypeParmDecl {{.*}} typename depth 0 index 0 Y
@@ -45,7 +45,7 @@ Out2<double>::AInner t(1.0);
// CHECK-NEXT: | | `-TemplateArgument type 'Y'
// CHECK-NEXT: | | `-SubstTemplateTypeParmType {{.*}} 'Y'
// CHECK-NEXT: | | |-FunctionTemplate {{.*}} '<deduction guide for Inner>'
-// CHECK-NEXT: | | `-TemplateTypeParmType {{.*}} 'Y' dependent depth 1 index 0
+// CHECK-NEXT: | | `-TemplateTypeParmType {{.*}} 'Y' dependent depth 0 index 0
// CHECK-NEXT: | | `-TemplateTypeParm {{.*}} 'Y'
// CHECK-NEXT: | |-CXXDeductionGuideDecl {{.*}} <deduction guide for AInner> 'auto (Y) -> Inner<Y>'
// CHECK-NEXT: | | `-ParmVarDecl {{.*}} 'Y'
diff --git a/clang/test/SemaTemplate/nested-implicit-deduction-guides.cpp b/clang/test/SemaTemplate/nested-implicit-deduction-guides.cpp
index 8e3b1b6b6ca77..dab801a305bb9 100644
--- a/clang/test/SemaTemplate/nested-implicit-deduction-guides.cpp
+++ b/clang/test/SemaTemplate/nested-implicit-deduction-guides.cpp
@@ -82,7 +82,7 @@ using NIL = nested_init_list<int>::B<int>;
// expected-error at +1 {{no viable constructor or deduction guide for deduction of template arguments of 'nested_init_list<int>::concept_fail'}}
nested_init_list<int>::concept_fail nil_invalid{1, ""};
// expected-note@#INIT_LIST_INNER_INVALID {{candidate template ignored: constraints not satisfied [with F = const char *]}}
-// expected-note@#INIT_LIST_INNER_INVALID_HEADER {{because 'const char *' does not satisfy 'False'}}
+// expected-note@#INIT_LIST_INNER_INVALID_HEADER {{because 'F' does not satisfy 'False'}}
// expected-note@#False {{because 'false' evaluated to false}}
// expected-note@#INIT_LIST_INNER_INVALID {{implicit deduction guide declared as 'template <False F> concept_fail(int, F) -> nested_init_list<int>::concept_fail<F>'}}
// expected-note@#INIT_LIST_INNER_INVALID {{candidate function template not viable: requires 1 argument, but 2 were provided}}
>From 4156562166990d30c33082f262fe39a985c90c0a Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 10 Apr 2026 14:51:40 +0200
Subject: [PATCH 2/4] [Clang][C++26] Implement P3865R1 - CTAD for type template
template parameters
As a DR to C++20.
Fixes #191380
---
clang/docs/ReleaseNotes.rst | 2 +
clang/include/clang/Sema/Sema.h | 6 ++
clang/lib/Sema/SemaInit.cpp | 80 +++++++++-----
clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 56 ++++++++++
.../test/SemaCXX/cxx2c-ctad-type-template.cpp | 102 ++++++++++++++++++
clang/www/cxx_status.html | 2 +-
6 files changed, 221 insertions(+), 27 deletions(-)
create mode 100644 clang/test/SemaCXX/cxx2c-ctad-type-template.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 8d60450f54669..d9e508d4ccf0c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -135,6 +135,8 @@ C++ Language Changes
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
+- Implemented `P3865R1 <https://wg21.link/P3865R1>`_ CTAD for type template template parameters.
+
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index da3d855a62360..ba077d175a787 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9139,6 +9139,12 @@ class Sema final : public SemaBase {
TypeSourceInfo *TInfo, const InitializedEntity &Entity,
const InitializationKind &Kind, MultiExprArg Init);
+ /// Build an alias for CTAD from type template parameters (C++26).
+ TypeAliasTemplateDecl *
+ BuildAliasForCTADFromTypeTemplateParameter(TemplateTemplateParmDecl *D,
+ TemplateName Replacement,
+ SourceLocation Loc);
+
///@}
//
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index e54a25405c816..aa8bda0eb2c99 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -10140,32 +10140,60 @@ QualType Sema::DeduceTemplateSpecializationFromInitializer(
if (TemplateName.isDependent())
return SubstAutoTypeSourceInfoDependent(TSInfo)->getType();
- // We can only perform deduction for class templates or alias templates.
- auto *Template =
- dyn_cast_or_null<ClassTemplateDecl>(TemplateName.getAsTemplateDecl());
- TemplateDecl *LookupTemplateDecl = Template;
- if (!Template) {
- if (auto *AliasTemplate = dyn_cast_or_null<TypeAliasTemplateDecl>(
- TemplateName.getAsTemplateDecl())) {
- DiagCompat(Kind.getLocation(), diag_compat::ctad_for_alias_templates);
- LookupTemplateDecl = AliasTemplate;
- auto UnderlyingType = AliasTemplate->getTemplatedDecl()
- ->getUnderlyingType()
- .getCanonicalType();
- // C++ [over.match.class.deduct#3]: ..., the defining-type-id of A must be
- // of the form
- // [typename] [nested-name-specifier] [template] simple-template-id
- if (const auto *TST =
- UnderlyingType->getAs<TemplateSpecializationType>()) {
- Template = dyn_cast_or_null<ClassTemplateDecl>(
- TST->getTemplateName().getAsTemplateDecl());
- } else if (const auto *RT = UnderlyingType->getAs<RecordType>()) {
- // Cases where template arguments in the RHS of the alias are not
- // dependent. e.g.
- // using AliasFoo = Foo<bool>;
- if (const auto *CTSD =
- llvm::dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl()))
- Template = CTSD->getSpecializedTemplate();
+ TemplateDecl *LookupTemplateDecl = nullptr;
+ ClassTemplateDecl *Template = nullptr;
+
+ // [C++26] [over.match.class.deduct]p3
+ // When resolving a placeholder for a deduced class type where the
+ // template-name designates a type template template parameter P.
+ //
+ // This is applied as a DR to C++20 (Aliases templates are technically a C++20
+ // feature)
+ if (const SubstTemplateTemplateParmStorage *SubstitutedTTP =
+ TemplateName.getAsSubstTemplateTemplateParm();
+ SubstitutedTTP && getLangOpts().CPlusPlus20) {
+
+ TypeAliasTemplateDecl *Alias = BuildAliasForCTADFromTypeTemplateParameter(
+ SubstitutedTTP->getParameter(), SubstitutedTTP->getReplacement(),
+ Kind.getLocation());
+ if (!Alias)
+ return QualType();
+
+ LookupTemplateDecl = Alias;
+ auto UnderlyingType =
+ Alias->getTemplatedDecl()->getUnderlyingType().getCanonicalType();
+ const auto *TST = UnderlyingType->getAs<TemplateSpecializationType>();
+ Template = dyn_cast_or_null<ClassTemplateDecl>(
+ TST->getTemplateName().getAsTemplateDecl());
+ } else {
+ // We can only perform deduction for class templates or alias templates.
+ Template =
+ dyn_cast_or_null<ClassTemplateDecl>(TemplateName.getAsTemplateDecl());
+ LookupTemplateDecl = Template;
+ if (!Template) {
+ if (auto *AliasTemplate = dyn_cast_or_null<TypeAliasTemplateDecl>(
+ TemplateName.getAsTemplateDecl())) {
+ DiagCompat(Kind.getLocation(), diag_compat::ctad_for_alias_templates);
+ LookupTemplateDecl = AliasTemplate;
+ auto UnderlyingType = AliasTemplate->getTemplatedDecl()
+ ->getUnderlyingType()
+ .getCanonicalType();
+ // C++ [over.match.class.deduct#3]: ..., the defining-type-id of A must
+ // be of the form
+ // [typename] [nested-name-specifier] [template] simple-template-id
+ if (const auto *TST =
+ UnderlyingType->getAs<TemplateSpecializationType>()) {
+ Template = dyn_cast_or_null<ClassTemplateDecl>(
+ TST->getTemplateName().getAsTemplateDecl());
+ } else if (const auto *RT = UnderlyingType->getAs<RecordType>()) {
+ // Cases where template arguments in the RHS of the alias are not
+ // dependent. e.g.
+ // using AliasFoo = Foo<bool>;
+ if (const auto *CTSD =
+ llvm::dyn_cast<ClassTemplateSpecializationDecl>(
+ RT->getDecl()))
+ Template = CTSD->getSpecializedTemplate();
+ }
}
}
}
diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
index 501d8426c7506..1ada59a6e603e 100644
--- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
+++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
@@ -1582,3 +1582,59 @@ void Sema::DeclareImplicitDeductionGuides(TemplateDecl *Template,
SavedContext.pop();
}
+
+TypeAliasTemplateDecl *Sema::BuildAliasForCTADFromTypeTemplateParameter(
+ TemplateTemplateParmDecl *D, TemplateName Replacement, SourceLocation Loc) {
+
+ // [C++26] [over.match.class.deduct]p3
+ // When resolving a placeholder for a deduced class type where the
+ // template-name designates a type template template parameter P, let A be an
+ // alias template whose template parameter list is that of P and whose
+ // defining-type-id designates the type template template argument with a
+ // simpletemplate-id in which the template-argument-list consists of a list of
+ // identifiers naming each template-parameter of P, with the argument being a
+ // pack expansion if the template-parameter is a pack. A is then used instead
+ // of the original template-name to resolve the placeholder
+
+ LocalInstantiationScope Scope(SemaRef);
+
+ auto &AST = SemaRef.getASTContext();
+ auto *Func = cast<NamedDecl>(CurContext);
+ auto *Ctx = CurContext->getParent();
+
+ MultiLevelTemplateArgumentList MTAL =
+ SemaRef.getTemplateInstantiationArgs(Func);
+ llvm::SmallVector<NamedDecl *> Parameters;
+ llvm::SmallVector<TemplateArgument> Args;
+ for (NamedDecl *P : D->getTemplateParameters()->asArray()) {
+ auto [Depth, Index] = getDepthAndIndex(P);
+ NamedDecl *NewParam =
+ transformTemplateParameter(*this, Ctx, P, MTAL, Index, Depth - 1);
+ if (!NewParam)
+ return nullptr;
+ Parameters.push_back(NewParam);
+ Args.push_back(SemaRef.Context.getInjectedTemplateArg(NewParam));
+ }
+
+ auto *ParamList =
+ TemplateParameterList::Create(AST, SourceLocation(), SourceLocation(),
+ Parameters, SourceLocation(), nullptr);
+
+ QualType Type = AST.getCanonicalType(AST.getTemplateSpecializationType(
+ ElaboratedTypeKeyword::Class, Replacement, Args, {}));
+
+ auto *Alias = TypeAliasDecl::Create(AST, Ctx, Loc, SourceLocation(), nullptr,
+ AST.getTrivialTypeSourceInfo(Type));
+ Alias->setImplicit(true);
+
+ auto *Template = TypeAliasTemplateDecl::Create(
+ AST, Ctx, Loc, Alias->getDeclName(), ParamList, Alias);
+
+ Alias->setDescribedAliasTemplate(Template);
+
+ Template->setImplicit(true);
+ Template->setLexicalDeclContext(Alias->getDeclContext());
+ Ctx->addDecl(Template);
+
+ return Template;
+}
diff --git a/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp b/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp
new file mode 100644
index 0000000000000..6eeab72532cfe
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp
@@ -0,0 +1,102 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++2c -verify %s
+
+
+namespace Ex1 {
+ template<typename T>
+ struct C {
+ C(T);
+ };
+ template<template<typename> class X>
+ void f() {
+ X x(1);
+ }
+ template void f<C>();
+}
+
+namespace Ex2 {
+template<typename ... T>
+struct C {
+ C(T ...);
+};
+template<template<typename> class X>
+void f() {
+ X x1{1};
+ X x2{1, 2}; // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'Ex2::C'}} \
+ // expected-note {{candidate function template not viable: requires 1 argument, but 2 were provided}} \
+ // expected-note {{implicit deduction guide declared as 'template <typename> requires __is_deducible(Ex2::(anonymous), Ex2::C<type-parameter-0-0>) (type-parameter-0-0) -> Ex2::C<type-parameter-0-0>'}} \
+ // expected-note {{candidate function template not viable: requires 1 argument, but 2 were provided}} \
+ // expected-note {{implicit deduction guide declared as 'template <typename> requires __is_deducible(Ex2::(anonymous), Ex2::C<type-parameter-0-0>) (Ex2::C<type-parameter-0-0>) -> Ex2::C<type-parameter-0-0}}
+}
+template void f<C>(); // expected-note {{in instantiation}}
+}
+
+namespace Ex3 {
+ template<typename T = int>
+ struct C {
+ C(int);
+ };
+ template<template<typename = long> class X>
+ void f() {
+ X x(1);
+ }
+ template void f<C>();
+}
+
+namespace Ex4 {
+template<int>
+struct A { };
+template<int I>
+struct C {
+ C(A<I>);
+};
+template<template<short> class X>
+void f() {
+ X x1{A<1>()}; // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'Ex4::C'}} \
+ // expected-note {{candidate template ignored: substitution failure: deduced non-type template argument does not have the same type as the corresponding template parameter ('int' vs 'short')}} \
+ // expected-note {{implicit deduction guide declared as 'template <short> requires __is_deducible(Ex4::(anonymous), Ex4::C<value-parameter-0-0>) (A<value-parameter-0-0>) -> Ex4::C<value-parameter-0-0>'}} \
+ // expected-note {{candidate template ignored: could not match 'Ex4::C' against 'A'}} \
+ // expected-note {{implicit deduction guide declared as 'template <short> requires __is_deducible(Ex4::(anonymous), Ex4::C<value-parameter-0-0>) (Ex4::C<value-parameter-0-0>) -> Ex4::C<value-parameter-0-0>'}}
+}
+template void f<C>(); // expected-note {{in instantiation}}
+}
+
+
+namespace CWG3003 {
+
+template <typename T> struct A { A(T); };
+
+template <typename T, template <typename> class TT = A>
+using Alias = TT<T>; // expected-note {{template is declared here}}
+
+template <typename T>
+using Alias2 = Alias<T>;
+
+void h() { Alias2 a(42); } // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'Alias2'}}
+void h2() { Alias a(42); } // expected-error {{alias template 'Alias' requires template arguments; argument deduction only allowed for class templates or alias templates}}
+
+}
+
+
+namespace WordingExample {
+template<typename ... Ts>
+struct Y {
+ Y();
+ Y(Ts ...);
+};
+template<template<typename T = char> class X>
+void f() {
+ X x0{};
+ X x1{1};
+ X x2{1, 2}; // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'WordingExample::Y'}} \
+ // expected-note {{candidate function template not viable: requires 1 argument, but 2 were provided}} \
+ // expected-note {{implicit deduction guide declared as 'template <typename T = char> requires __is_deducible(WordingExample::(anonymous), WordingExample::Y<T>) (T) -> WordingExample::Y<T>'}} \
+ // expected-note {{candidate function template not viable: requires 1 argument, but 2 were provided}} \
+ // expected-note {{implicit deduction guide declared as 'template <typename T = char> requires __is_deducible(WordingExample::(anonymous), WordingExample::Y<T>) (WordingExample::Y<T>) -> WordingExample::Y<T>'}} \
+ // expected-note {{candidate function template not viable: requires 0 arguments, but 2 were provided}} \
+ // expected-note {{implicit deduction guide declared as 'template <typename T = char> requires __is_deducible(WordingExample::(anonymous), WordingExample::Y<T>) () -> WordingExample::Y<T>'}}
+};
+template void f<Y>(); // expected-note {{in instantiation}}
+
+}
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 2c834b07f9a8f..87c58d9ea3851 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -352,7 +352,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>CTAD for type template template parameters</td>
<td><a href="https://wg21.link/P3865">P3865R3</a>(<a href="#dr">DR</a>)</td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 23</td>
</tr>
<tr>
<td>Adjustments to Union Lifetime Rules</td>
>From 81744347c0945cb894a79296879a3365377e3698 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Sat, 11 Apr 2026 13:08:44 +0200
Subject: [PATCH 3/4] fix libc++ crash
---
clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 7 +++--
.../test/SemaCXX/cxx2c-ctad-type-template.cpp | 30 +++++++++++++++++++
2 files changed, 34 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
index 1ada59a6e603e..98aaa3382e2ad 100644
--- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
+++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
@@ -1599,11 +1599,12 @@ TypeAliasTemplateDecl *Sema::BuildAliasForCTADFromTypeTemplateParameter(
LocalInstantiationScope Scope(SemaRef);
auto &AST = SemaRef.getASTContext();
- auto *Func = cast<NamedDecl>(CurContext);
auto *Ctx = CurContext->getParent();
- MultiLevelTemplateArgumentList MTAL =
- SemaRef.getTemplateInstantiationArgs(Func);
+ MultiLevelTemplateArgumentList MTAL;
+ if (NamedDecl *Func = dyn_cast<NamedDecl>(CurContext))
+ MTAL = SemaRef.getTemplateInstantiationArgs(Func);
+
llvm::SmallVector<NamedDecl *> Parameters;
llvm::SmallVector<TemplateArgument> Args;
for (NamedDecl *P : D->getTemplateParameters()->asArray()) {
diff --git a/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp b/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp
index 6eeab72532cfe..929c150d91dde 100644
--- a/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp
+++ b/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp
@@ -100,3 +100,33 @@ void f() {
template void f<Y>(); // expected-note {{in instantiation}}
}
+
+namespace Regression1 {
+
+template <typename>
+struct __iter_concept_impl;
+template <typename _Iter>
+ requires requires { typename _Iter; }
+struct __iter_concept_impl<_Iter>;
+template <typename _Iter>
+concept input_iterator = true;
+
+template <typename _Tp>
+concept input_range = true;
+
+template <template <typename> typename _Cont>
+using _DeduceExpr1 = decltype(_Cont());
+
+template <template <typename> typename _Cont, input_range _Rg>
+auto to(_Rg) {
+ auto _ = requires { typename _DeduceExpr1<_Cont>; };
+}
+
+template <typename>
+struct vector;
+
+void test() {
+ int range;
+ to<vector>(range);
+}
+}
>From 5444ce2098e293928283ea84e847dd66b1d5c891 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Thu, 23 Apr 2026 11:51:53 +0200
Subject: [PATCH 4/4] address feedback
---
clang/docs/ReleaseNotes.rst | 1 +
clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 18 +++++++++----
.../test/SemaCXX/cxx2c-ctad-type-template.cpp | 25 +++++++++++++++++++
3 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d9e508d4ccf0c..52939e54a9ff5 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -136,6 +136,7 @@ C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
- Implemented `P3865R1 <https://wg21.link/P3865R1>`_ CTAD for type template template parameters.
+ This is implemented as a DR to C++20.
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
index 98aaa3382e2ad..c3f896aeb1e5f 100644
--- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
+++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
@@ -1598,8 +1598,13 @@ TypeAliasTemplateDecl *Sema::BuildAliasForCTADFromTypeTemplateParameter(
LocalInstantiationScope Scope(SemaRef);
- auto &AST = SemaRef.getASTContext();
- auto *Ctx = CurContext->getParent();
+ ASTContext &AST = getASTContext();
+ DeclContext *Ctx = CurContext;
+
+ while(Ctx->isRequiresExprBody())
+ Ctx = Ctx->getParent();
+
+ Ctx = Ctx->isFileContext() ? Ctx : Ctx->getLexicalParent();
MultiLevelTemplateArgumentList MTAL;
if (NamedDecl *Func = dyn_cast<NamedDecl>(CurContext))
@@ -1610,7 +1615,7 @@ TypeAliasTemplateDecl *Sema::BuildAliasForCTADFromTypeTemplateParameter(
for (NamedDecl *P : D->getTemplateParameters()->asArray()) {
auto [Depth, Index] = getDepthAndIndex(P);
NamedDecl *NewParam =
- transformTemplateParameter(*this, Ctx, P, MTAL, Index, Depth - 1);
+ transformTemplateParameter(*this, Ctx, P, MLTAL, Index, Depth? Depth -1 : 0);
if (!NewParam)
return nullptr;
Parameters.push_back(NewParam);
@@ -1619,12 +1624,15 @@ TypeAliasTemplateDecl *Sema::BuildAliasForCTADFromTypeTemplateParameter(
auto *ParamList =
TemplateParameterList::Create(AST, SourceLocation(), SourceLocation(),
- Parameters, SourceLocation(), nullptr);
+ Parameters, SourceLocation(),
+ /*RequiresClause=*/nullptr);
QualType Type = AST.getCanonicalType(AST.getTemplateSpecializationType(
ElaboratedTypeKeyword::Class, Replacement, Args, {}));
- auto *Alias = TypeAliasDecl::Create(AST, Ctx, Loc, SourceLocation(), nullptr,
+ // FIXME: Have a flag to distinguish such special type alias declarations.
+ auto *Alias = TypeAliasDecl::Create(AST, Ctx, Loc, /*IdLoc=*/SourceLocation(),
+ /*Id=*/nullptr,
AST.getTrivialTypeSourceInfo(Type));
Alias->setImplicit(true);
diff --git a/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp b/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp
index 929c150d91dde..1004290c6f715 100644
--- a/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp
+++ b/clang/test/SemaCXX/cxx2c-ctad-type-template.cpp
@@ -130,3 +130,28 @@ void test() {
to<vector>(range);
}
}
+
+
+namespace Regression2 {
+template <template <class...> class _Container, typename _Range>
+struct _Deducer {
+ static auto __deduce_func() {
+ static_assert(requires { _Container(); },
+ "ranges::to: unable to deduce the container type from "
+ "the template template argument.");
+ }
+ using type = decltype(__deduce_func);
+};
+template <template <class...> class _Container, typename _Range>
+auto to(_Range) {
+ using _DeduceExpr = _Deducer<_Container, _Range>::type;
+}
+
+template <class = int> struct vector {};
+
+void test() {
+ vector<int> range;
+ to<vector>(range);
+}
+
+}
More information about the cfe-commits
mailing list