[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