[clang] [Clang] Keep lambdas in default template arguments dependent until used (PR #222541)

via cfe-commits cfe-commits at lists.llvm.org
Thu Sep 10 03:54:55 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Akash Manna (akash-manna-sky)

<details>
<summary>Changes</summary>

Fixes #<!-- -->176405

A lambda in the default argument of a template parameter is parsed as dependent, with its own template parameters one depth below the parameter list (#<!-- -->62611): it can only be finished once the default argument is substituted for a use of the template. That got lost when the parameter list belongs to a member template of a class template. Instantiating the enclosing class substitutes the default argument with just the outer levels, and `TransformLambdaExpr` then recomputes the closure's dependence from the `DeclContext`, which is the concrete specialization by now. So the generic lambda was rebuilt as non-dependent while its template parameters still sat at depth 1, and `[]<typename... U>(U...) {}()` was resolved on the spot: deduction never saw `U`, the pack was never expanded, and `BuildCXXDefaultArgExpr` asserted on a parameter that has no default. The non-pack variant shows the same problem as a bogus "couldn't infer template argument 'U'" on valid code.

Default arguments of a template parameter list being instantiated now go through `Sema::SubstTemplateParameterDefaultArgument`, which flags the `TemplateInstantiator` so that `ComputeLambdaDependency` keeps any lambda it transforms `LDK_AlwaysDependent`, the same way the alias-template case is handled. The instantiated member template's default argument then looks exactly like one parsed in a non-template class: the lambda is built and called only when the default argument is used, with the parameter list's own level present and the depths lined up. Deduction, overload resolution and default-argument building are unchanged.


---
Full diff: https://github.com/llvm/llvm-project/pull/222541.diff


5 Files Affected:

- (modified) clang/docs/ReleaseNotes.md (+5) 
- (modified) clang/include/clang/Sema/Sema.h (+8) 
- (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+23) 
- (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+4-4) 
- (modified) clang/test/SemaCXX/cxx2a-template-lambdas.cpp (+52) 


``````````diff
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 3cca316a91d4d..97944a8870206 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -676,6 +676,11 @@ features cannot lower the translation-unit ABI level;
   class with an invalid non-static data member, such as one qualified with an
   address space. (#GH194605)
 
+- Fixed an assertion when instantiating a class template whose member template
+  has a default template argument that calls a generic lambda, e.g.
+  ``template <auto = []<typename... U>(U...) {}()> struct X;``. The lambda now
+  remains dependent until the default argument is used. (#GH176405)
+
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 4ff4c669a6b70..5d9f9cf683018 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -13576,6 +13576,14 @@ class Sema final : public SemaBase {
                              TemplateArgumentLoc &Output,
                              SourceLocation Loc = {},
                              const DeclarationName &Entity = {});
+
+  /// Substitute into the default argument of a template parameter as part of
+  /// instantiating its template parameter list. Lambdas within the default
+  /// argument stay dependent, as they were when parsed.
+  bool SubstTemplateParameterDefaultArgument(
+      const TemplateArgumentLoc &Input,
+      const MultiLevelTemplateArgumentList &TemplateArgs,
+      TemplateArgumentLoc &Output);
   bool
   SubstTemplateArguments(ArrayRef<TemplateArgumentLoc> Args,
                          const MultiLevelTemplateArgumentList &TemplateArgs,
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index ffbe8bb0506bc..db13b05c81d1c 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1317,6 +1317,9 @@ namespace {
     // Whether to evaluate the C++20 constraints or simply substitute into them.
     bool EvaluateConstraints = true;
     bool EvaluateLambdaConstraint = false;
+    // Whether we are substituting into the default argument of a template
+    // parameter whose template parameter list is being instantiated.
+    bool InTemplateParameterDefaultArgument = false;
     // Whether Substitution was Incomplete, that is, we tried to substitute in
     // any user provided template arguments which were null.
     bool IsIncomplete = false;
@@ -1352,6 +1355,10 @@ namespace {
       return EvaluateConstraints;
     }
 
+    void setInTemplateParameterDefaultArgument(bool B) {
+      InTemplateParameterDefaultArgument = B;
+    }
+
     inline static struct ForParameterMappingSubstitution_t {
     } ForParameterMappingSubstitution;
 
@@ -1760,6 +1767,12 @@ namespace {
 
     CXXRecordDecl::LambdaDependencyKind
     ComputeLambdaDependency(LambdaScopeInfo *LSI) {
+      // A lambda in the default argument of a template parameter is dependent
+      // when parsed (it is within a template parameter list) and remains so
+      // while that parameter list is instantiated without being substituted
+      // itself, e.g. for a member template of a class being instantiated.
+      if (InTemplateParameterDefaultArgument)
+        return CXXRecordDecl::LambdaDependencyKind::LDK_AlwaysDependent;
       if (auto TypeAlias =
               TemplateInstArgsHelpers::getEnclosingTypeAliasTemplateDecl(
                   getSema());
@@ -4471,6 +4484,16 @@ bool Sema::SubstTemplateArgument(
   return Instantiator.TransformTemplateArgument(Input, Output);
 }
 
+bool Sema::SubstTemplateParameterDefaultArgument(
+    const TemplateArgumentLoc &Input,
+    const MultiLevelTemplateArgumentList &TemplateArgs,
+    TemplateArgumentLoc &Output) {
+  TemplateInstantiator Instantiator(*this, TemplateArgs, SourceLocation(),
+                                    DeclarationName());
+  Instantiator.setInTemplateParameterDefaultArgument(true);
+  return Instantiator.TransformTemplateArgument(Input, Output);
+}
+
 bool Sema::SubstTemplateArguments(
     ArrayRef<TemplateArgumentLoc> Args,
     const MultiLevelTemplateArgumentList &TemplateArgs,
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 7668b75e836e4..e9336fd823129 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -3862,8 +3862,8 @@ Decl *TemplateDeclInstantiator::VisitTemplateTypeParmDecl(
   }
   if (D->hasDefaultArgument() && !D->defaultArgumentWasInherited()) {
     TemplateArgumentLoc Output;
-    if (!SemaRef.SubstTemplateArgument(D->getDefaultArgument(), TemplateArgs,
-                                       Output))
+    if (!SemaRef.SubstTemplateParameterDefaultArgument(D->getDefaultArgument(),
+                                                       TemplateArgs, Output))
       Inst->setDefaultArgument(SemaRef.getASTContext(), Output);
   }
 
@@ -4026,8 +4026,8 @@ Decl *TemplateDeclInstantiator::VisitNonTypeTemplateParmDecl(
     EnterExpressionEvaluationContext ConstantEvaluated(
         SemaRef, Sema::ExpressionEvaluationContext::ConstantEvaluated);
     TemplateArgumentLoc Result;
-    if (!SemaRef.SubstTemplateArgument(D->getDefaultArgument(), TemplateArgs,
-                                       Result))
+    if (!SemaRef.SubstTemplateParameterDefaultArgument(D->getDefaultArgument(),
+                                                       TemplateArgs, Result))
       Param->setDefaultArgument(SemaRef.Context, Result);
   }
 
diff --git a/clang/test/SemaCXX/cxx2a-template-lambdas.cpp b/clang/test/SemaCXX/cxx2a-template-lambdas.cpp
index 45d265e2cdc2b..2f7eb9ddcca48 100644
--- a/clang/test/SemaCXX/cxx2a-template-lambdas.cpp
+++ b/clang/test/SemaCXX/cxx2a-template-lambdas.cpp
@@ -96,6 +96,58 @@ void foo() {
   {return {};}(1);
 }
 
+}
+
+namespace GH176405 {
+template <int> struct bad {
+  template <auto = []<typename... U>(U...) {}()> struct X;
+  static int f() { return 0; }
+};
+int y = bad<0>::f();
+
+template <int N> struct S {
+  template <auto V = []<typename... U>(U...) { return sizeof...(U) + N; }()>
+  struct A { static constexpr auto value = V; };
+  template <auto V = []<typename U>(U u) { return u + N; }(41)>
+  struct B { static constexpr auto value = V; };
+  template <auto V = [](auto... x) { return sizeof...(x) + N; }(1, 2)>
+  struct C { static constexpr auto value = V; };
+  template <typename T, auto V = [] { return sizeof(T) + N; }()>
+  struct D { static constexpr auto value = V; };
+  template <typename T = decltype([]<typename... U>(U...) { return N; }())>
+  struct E { using type = T; };
+  template <auto V = []<typename... U>(U...) { return sizeof...(U) + N; }()>
+  static constexpr auto f() { return V; }
+  template <auto V = []<typename... U>(U...) { return sizeof...(U) + N; }()>
+  static constexpr auto var = V;
+  template <auto V = []<typename... U>(U...) { return sizeof...(U) + N; }()>
+  using alias = A<V>;
+};
+static_assert(S<1>::A<>::value == 1);
+static_assert(S<1>::A<5>::value == 5);
+static_assert(S<1>::B<>::value == 42);
+static_assert(S<1>::C<>::value == 3);
+static_assert(S<1>::D<int>::value == sizeof(int) + 1);
+static_assert(__is_same(S<1>::E<>::type, int));
+static_assert(S<1>::f() == 1);
+static_assert(S<1>::var<> == 1);
+static_assert(S<1>::alias<>::value == 1);
+
+template <int N> struct Outer {
+  template <int M> struct Inner {
+    template <auto V = []<typename... U>(U...) { return N + M; }()>
+    struct X { static constexpr auto value = V; };
+  };
+};
+static_assert(Outer<1>::Inner<2>::X<>::value == 3);
+
+template <int N> constexpr auto g() {
+  auto l = []<typename T = decltype([]<typename V>(V v) { return v; }(N))>() {
+    return T{};
+  };
+  return l();
+}
+static_assert(g<1>() == 0);
 }
 #endif
 

``````````

</details>


https://github.com/llvm/llvm-project/pull/222541


More information about the cfe-commits mailing list