[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