https://github.com/bassiounix updated https://github.com/llvm/llvm-project/pull/193056
>From 39b87cfe469336770e1d94e6bea5f50f6fae75d4 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Mon, 20 Apr 2026 20:50:53 +0200 Subject: [PATCH 01/12] [Clang][C++20] Add full support to deduction guides --- clang/lib/Frontend/InitPreprocessor.cpp | 3 +-- clang/test/Lexer/cxx-features.cpp | 3 +-- clang/www/cxx_status.html | 8 +------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 1ccd74314f373..0a5ab5c21cce6 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -686,7 +686,6 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, Builder.defineMacro("__cpp_noexcept_function_type", "201510L"); Builder.defineMacro("__cpp_capture_star_this", "201603L"); Builder.defineMacro("__cpp_if_constexpr", "201606L"); - Builder.defineMacro("__cpp_deduction_guides", "201703L"); // (not latest) Builder.defineMacro("__cpp_template_auto", "201606L"); // (old name) Builder.defineMacro("__cpp_namespace_attributes", "201411L"); Builder.defineMacro("__cpp_enumerator_attributes", "201411L"); @@ -708,7 +707,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, // C++20 features. if (LangOpts.CPlusPlus20) { Builder.defineMacro("__cpp_aggregate_paren_init", "201902L"); - + Builder.defineMacro("__cpp_deduction_guides", "201907L"); Builder.defineMacro("__cpp_concepts", "202002"); Builder.defineMacro("__cpp_conditional_explicit", "201806L"); Builder.defineMacro("__cpp_consteval", "202211L"); diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp index 8eb9ea032879c..f8440ba6c641e 100644 --- a/clang/test/Lexer/cxx-features.cpp +++ b/clang/test/Lexer/cxx-features.cpp @@ -193,8 +193,7 @@ #error "wrong value for __cpp_if_constexpr" #endif -#if check(deduction_guides, 0, 0, 0, 201703, 201703, 201703, 201703) -// FIXME: 201907 in C++20 +#if check(deduction_guides, 0, 0, 0, 201907, 201907, 201907, 201907) #error "wrong value for __cpp_deduction_guides" #endif diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 2c834b07f9a8f..eac8689da39fb 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -1030,13 +1030,7 @@ <h2 id="cxx20">C++20 implementation status</h2> <tr> <td>Class template argument deduction for alias templates</td> <td><a href="https://wg21.link/p1814r0">P1814R0</a></td> - <td class="partial" align="center"> - <details> - <summary>Clang 19 (Partial)</summary> - This feature has been initially completed, but the feature macro - __cpp_deduction_guides has not been updated. - </details> - </td> + <td class="full" align="center">Clang 22</td> </tr> <tr> <td>Permit conversions to arrays of unknown bound</td> >From d86ad85b0e6a6b9c167fd04b00fe5f291ff30958 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Mon, 20 Apr 2026 22:35:16 +0200 Subject: [PATCH 02/12] fix build --- clang/test/Lexer/cxx-features.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp index f8440ba6c641e..c99b0eca3af22 100644 --- a/clang/test/Lexer/cxx-features.cpp +++ b/clang/test/Lexer/cxx-features.cpp @@ -94,6 +94,10 @@ #error "wrong value for __cpp_aggregate_paren_init" #endif +#if check(deduction_guides, 0, 0, 0, 0, 201907, 201907, 201907) +#error "wrong value for __cpp_deduction_guides" +#endif + #if defined(CHAR8_T) ? check(char8_t, 202207, 202207, 202207, 202207, 202207, 202207, 202207) : \ defined(NO_CHAR8_T) ? check(char8_t, 0, 0, 0, 0, 0, 0, 0) : \ check(char8_t, 0, 0, 0, 0, 202207, 202207, 202207) @@ -193,10 +197,6 @@ #error "wrong value for __cpp_if_constexpr" #endif -#if check(deduction_guides, 0, 0, 0, 201907, 201907, 201907, 201907) -#error "wrong value for __cpp_deduction_guides" -#endif - #if check(nontype_template_parameter_auto, 0, 0, 0, 201606, 201606, 201606, 201606) #error "wrong value for __cpp_nontype_template_parameter_auto" #endif >From 971ce4bf50a10750dd894cef32c489b037d2e8d6 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Tue, 21 Apr 2026 15:42:00 +0200 Subject: [PATCH 03/12] enable ctad for both c++17 and c++20 --- clang/lib/Frontend/InitPreprocessor.cpp | 1 + clang/test/Lexer/cxx-features.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 0a5ab5c21cce6..0bab87c8a8d1b 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -686,6 +686,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, Builder.defineMacro("__cpp_noexcept_function_type", "201510L"); Builder.defineMacro("__cpp_capture_star_this", "201603L"); Builder.defineMacro("__cpp_if_constexpr", "201606L"); + Builder.defineMacro("__cpp_deduction_guides", "201703L"); Builder.defineMacro("__cpp_template_auto", "201606L"); // (old name) Builder.defineMacro("__cpp_namespace_attributes", "201411L"); Builder.defineMacro("__cpp_enumerator_attributes", "201411L"); diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp index c99b0eca3af22..494b86d024b30 100644 --- a/clang/test/Lexer/cxx-features.cpp +++ b/clang/test/Lexer/cxx-features.cpp @@ -94,7 +94,7 @@ #error "wrong value for __cpp_aggregate_paren_init" #endif -#if check(deduction_guides, 0, 0, 0, 0, 201907, 201907, 201907) +#if check(deduction_guides, 0, 0, 0, 201703, 201907, 201907, 201907) #error "wrong value for __cpp_deduction_guides" #endif >From df7eb6223031877af5628b9bcc0cd54ba9629b83 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Tue, 21 Apr 2026 15:42:17 +0200 Subject: [PATCH 04/12] increase clang support version --- clang/www/cxx_status.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index eac8689da39fb..0581370ea751b 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -1030,7 +1030,7 @@ <h2 id="cxx20">C++20 implementation status</h2> <tr> <td>Class template argument deduction for alias templates</td> <td><a href="https://wg21.link/p1814r0">P1814R0</a></td> - <td class="full" align="center">Clang 22</td> + <td class="full" align="center">Clang 23</td> </tr> <tr> <td>Permit conversions to arrays of unknown bound</td> >From e9bfd0893afd468860cee6ae5fddde9bfdff526e Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Wed, 2 Sep 2026 22:22:34 +0300 Subject: [PATCH 05/12] Fix GH125821 --- clang/docs/ReleaseNotes.md | 7 ++++ clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 35 ++++++++++++++++--- clang/test/SemaCXX/cxx20-ctad-type-alias.cpp | 4 +-- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index cd7f40aec112d..79386c1c8924f 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -470,6 +470,13 @@ features cannot lower the translation-unit ABI level; - Fixed an issue where `__typeof__` incorrectly rejected cv-qualified function types. +- Class template argument deduction through an alias template now works when + the right-hand side of the alias names another alias template that cannot + have deduction guides of its own, such as an identity alias + (`template <class T> using Identity = T;`). The deduction guides are now + derived from the first template in the chain that can have them, per the + equivalence rule of `[temp.alias]p2`, matching GCC. (#GH125821) + - Fixed a bug where top-level CV qualifiers (such as ``const``) were dropped from pointers modified by Microsoft pointer attributes (like ``__ptr32`` and ``__ptr64``) and WebAssembly's ``__funcref``. - Fixed an issue where we tried to compare invalid NTTPs for variable declarations, which ended up in hitting an assertion with a constrained non-plain-auto NTTP, which we don't quite implement yet. (#GH208658) diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp index 63d6759bc5490..e133ae0b1e9b0 100644 --- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp +++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp @@ -1177,13 +1177,40 @@ getRHSTemplateDeclAndArgs(Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate) { auto RhsType = AliasTemplate->getTemplatedDecl()->getUnderlyingType(); TemplateDecl *Template = nullptr; llvm::ArrayRef<TemplateArgument> AliasRhsTemplateArgs; - if (const auto *TST = RhsType->getAs<TemplateSpecializationType>()) { + const auto *TST = RhsType->getAs<TemplateSpecializationType>(); + + // The RHS of the alias may name another alias template that can never have + // deduction guides of its own, because its defining-type-id is not of the + // form + // [typename] [nested-name-specifier] [template] simple-template-id + // as required by [over.match.class.deduct]p3. e.g. + // template <typename T> + // using Identity = T; + // template <typename T> + // using C = Identity<Foo<T>>; + // Per [temp.alias]p2, Identity<Foo<T>> is equivalent to Foo<T>, so step + // through such aliases and derive the deduction guides from the first + // template that can actually have them (GH125821). + while (TST) { + auto *RhsAlias = dyn_cast_or_null<TypeAliasTemplateDecl>( + TST->getTemplateName().getAsTemplateDecl()); + if (!RhsAlias || getRHSTemplateDeclAndArgs(SemaRef, RhsAlias).first) + break; + RhsType = TST->desugar(); + TST = RhsType->getAs<TemplateSpecializationType>(); + } + + if (TST) { // Cases where the RHS of the alias is dependent. e.g. // template<typename T> // using AliasFoo1 = Foo<T>; // a class/type alias template specialization - Template = TST->getTemplateName().getAsTemplateDecl(); - AliasRhsTemplateArgs = - TST->getAsNonAliasTemplateSpecializationType()->template_arguments(); + // The RHS may not desugar to a template specialization at all (e.g. an + // alias of the form 'T*' whose specialization ends up being a pointer); + // in that case, there is no template to derive the guides from. + if (const auto *RhsTST = TST->getAsNonAliasTemplateSpecializationType()) { + Template = TST->getTemplateName().getAsTemplateDecl(); + AliasRhsTemplateArgs = RhsTST->template_arguments(); + } } else if (const auto *RT = RhsType->getAs<RecordType>()) { // Cases where template arguments in the RHS of the alias are not // dependent. e.g. diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp index 78911cbaed67b..e0f59be663b2f 100644 --- a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp +++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp @@ -553,7 +553,6 @@ void foo() { test<{1, 2, 3}>(); } } // namespace GH113518 -// FIXME: This is accepted by GCC: https://gcc.godbolt.org/z/f3rMfbacz namespace GH125821 { template<typename T> struct A { A(T){} }; @@ -564,7 +563,8 @@ using Proxy = T; template<typename T> using C = Proxy< A<T> >; -C test{ 42 }; // expected-error {{no viable constructor or deduction guide for deduction of template arguments}} +C test{ 42 }; +static_assert(__is_same(decltype(test), A<int>)); } // namespace GH125821 >From 83b7daebf896606b9bb518591e94a97160b0626a Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Wed, 2 Sep 2026 22:25:02 +0300 Subject: [PATCH 06/12] pump up clang version --- clang/www/cxx_status.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 6c045937add3e..9617a8f833fe1 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -1154,7 +1154,7 @@ <h2 id="cxx20">C++20 implementation status</h2> <tr> <td>Class template argument deduction for alias templates</td> <td><a href="https://wg21.link/p1814r0">P1814R0</a></td> - <td class="full" align="center">Clang 23</td> + <td class="unreleased" align="center">Clang 24</td> </tr> <tr> <td>Permit conversions to arrays of unknown bound</td> >From b306f8160976b89f0bd2d8eaf0f42fa6d0b5bd88 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Thu, 3 Sep 2026 06:03:55 +0300 Subject: [PATCH 07/12] Allow default arguments deduction from the return type of the underlying deduction guide. This change ensures that template parameters that cannot be deduced from constructor arguments receive appropriate default values. --- clang/docs/ReleaseNotes.md | 15 +- clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 454 ++++++++++++++++-- clang/test/AST/ast-dump-ctad-alias.cpp | 30 ++ clang/test/SemaCXX/cxx20-ctad-type-alias.cpp | 102 ++++ 4 files changed, 560 insertions(+), 41 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 79386c1c8924f..21578b5eb87e1 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -73,7 +73,7 @@ features cannot lower the translation-unit ABI level; - On SPARC, a `_Complex` value with an integer element type is now passed and returned packed into the one or two integer registers it fits in, matching GCC. Clang previously passed such a value indirectly and returned it with one part - per register. + per register. `-fclang-abi-compat=23` restores the previous behavior. (#GH212340) - On SPARC64, a `_Complex char` or `_Complex short` is now @@ -119,7 +119,7 @@ features cannot lower the translation-unit ABI level; - `CompletionString.availability` now returns instances of `AvailabilityKind`. As a result, the `__str__` representation of its return values changed. - Like other libclang enums, it now follows the `CompletionChunkKind.VARIANT_NAME` scheme instead of `VariantName`. + Like other libclang enums, it now follows the `CompletionChunkKind.VARIANT_NAME` scheme instead of `VariantName`. ### OpenCL Potentially Breaking Changes @@ -477,6 +477,13 @@ features cannot lower the translation-unit ABI level; derived from the first template in the chain that can have them, per the equivalence rule of `[temp.alias]p2`, matching GCC. (#GH125821) +- Class template argument deduction through an alias template now works when + a template parameter of the alias only appears in the synthesized deduction + guide through default template arguments. Such a template parameter now gets a default + template argument deduced from the return type of the underlying deduction + guide (here `Key` becomes the iterator's value type), instead of being + undeducible. + - Fixed a bug where top-level CV qualifiers (such as ``const``) were dropped from pointers modified by Microsoft pointer attributes (like ``__ptr32`` and ``__ptr64``) and WebAssembly's ``__funcref``. - Fixed an issue where we tried to compare invalid NTTPs for variable declarations, which ended up in hitting an assertion with a constrained non-plain-auto NTTP, which we don't quite implement yet. (#GH208658) @@ -491,9 +498,9 @@ features cannot lower the translation-unit ABI level; producing a spurious "no matching function" error with no candidate notes. (#GH210822) -- Fixed a crash when module directive export module foo not following a +- Fixed a crash when module directive export module foo not following a semicolon and there are no rest pp-tokens in current module file. (#GH187771) - + - Fixed a crash when a lambda parameter pack was given a default argument that is a pack expansion referencing an enclosing function's parameter pack (e.g. `[](Types... = args...) {}`). Clang now diagnoses the illegal default diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp index e133ae0b1e9b0..05b99c9374148 100644 --- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp +++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp @@ -956,19 +956,61 @@ llvm::DenseSet<const NamedDecl *> getSourceDeductionGuides(DeclarationName Name, return Result; } +// Marker for a template parameter that doesn't appear in the synthesized +// deduction guide f' of an alias template. +constexpr unsigned InvalidFPrimeIndex = -1; + +// A template parameter of the synthesized deduction guide f' of an alias +// template A, before it is created. +struct FPrimeTemplateParamRef { + // Whether this is a template parameter of A, as opposed to a non-deduced + // template parameter of the underlying deduction guide f. + bool IsAliasParam; + // The index of the template parameter in the template parameter list of A or + // f, respectively. + unsigned Index; + // For a non-deduced template parameter of f: the deduced template parameters + // of f that it refers to. + llvm::SmallBitVector DeducedFParamsUsed; +}; + +static bool hasDefaultArgument(const NamedDecl *Param) { + if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) + return TTP->hasDefaultArgument(); + if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(Param)) + return NTTP->hasDefaultArgument(); + return cast<TemplateTemplateParmDecl>(Param)->hasDefaultArgument(); +} + +static void setDefaultArgument(ASTContext &Context, NamedDecl *Param, + const TemplateArgumentLoc &DefArg) { + if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) + TTP->setDefaultArgument(Context, DefArg); + else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(Param)) + NTTP->setDefaultArgument(Context, DefArg); + else + cast<TemplateTemplateParmDecl>(Param)->setDefaultArgument(Context, DefArg); +} + // Build the associated constraints for the alias deduction guides. // C++ [over.match.class.deduct]p3.3: // The associated constraints ([temp.constr.decl]) are the conjunction of the // associated constraints of g and a constraint that is satisfied if and only // if the arguments of A are deducible (see below) from the return type. // +// AliasParamFPrimeIndex and FParamFPrimeIndex give the index in f' of the +// template parameters of the alias template and of the non-deduced template +// parameters of F, respectively (InvalidFPrimeIndex for those not in f'). +// // The return result is expected to be the require-clause for the synthesized // alias deduction guide. Expr * buildAssociatedConstraints(Sema &SemaRef, FunctionTemplateDecl *F, TypeAliasTemplateDecl *AliasTemplate, ArrayRef<DeducedTemplateArgument> DeduceResults, - unsigned FirstUndeducedParamIdx, Expr *IsDeducible) { + ArrayRef<unsigned> AliasParamFPrimeIndex, + ArrayRef<unsigned> FParamFPrimeIndex, + Expr *IsDeducible) { Expr *RC = F->getTemplateParameters()->getRequiresClause(); if (!RC) return IsDeducible; @@ -1002,15 +1044,19 @@ buildAssociatedConstraints(Sema &SemaRef, FunctionTemplateDecl *F, // build template arguments refer to them. SmallVector<TemplateArgument> AdjustedAliasTemplateArgs; - for (auto *TP : *AliasTemplate->getTemplateParameters()) { + for (auto [Index, TP] : + llvm::enumerate(*AliasTemplate->getTemplateParameters())) { // Rebuild any internal references to earlier parameters and reindex - // as we go. + // as we go. Template parameters of the alias that don't appear in f' are + // not referred to by the deduced template arguments; keep their index. MultiLevelTemplateArgumentList Args; Args.setKind(TemplateSubstitutionKind::Rewrite); Args.addOuterTemplateArguments(AdjustedAliasTemplateArgs); + unsigned NewIndex = AliasParamFPrimeIndex[Index] != InvalidFPrimeIndex + ? AliasParamFPrimeIndex[Index] + : Index; NamedDecl *NewParam = transformTemplateParameter( - SemaRef, AliasTemplate->getDeclContext(), TP, Args, - /*NewIndex=*/AdjustedAliasTemplateArgs.size(), + SemaRef, AliasTemplate->getDeclContext(), TP, Args, NewIndex, getDepthAndIndex(TP).first + AdjustDepth); TemplateArgument NewTemplateArgument = @@ -1036,9 +1082,8 @@ buildAssociatedConstraints(Sema &SemaRef, FunctionTemplateDecl *F, // Rebuild the template parameter with updated depth and index. NamedDecl *NewParam = transformTemplateParameter(SemaRef, F->getDeclContext(), TP, Args, - /*NewIndex=*/FirstUndeducedParamIdx, + /*NewIndex=*/FParamFPrimeIndex[Index], getDepthAndIndex(TP).first + AdjustDepth); - FirstUndeducedParamIdx += 1; assert(TemplateArgsForBuildingRC[Index].isNull()); TemplateArgsForBuildingRC[Index] = Context.getInjectedTemplateArg(NewParam); @@ -1237,6 +1282,255 @@ bool IsNonDeducedArgument(const TemplateArgument &TA) { llvm::any_of(TA.pack_elements(), IsNonDeducedArgument)); } +// Synthesize default template arguments for the template parameters of the +// alias template A that appear in the deduction guide f' without having a +// default template argument. +// +// Such a template parameter cannot be deduced from the function parameters of +// f' if it only appears in f' through the default template arguments of other +// template parameters of A, e.g. +// +// template <class Key, class Hash = std::hash<Key>> +// using MySet = std::unordered_set<Key, Hash>; +// +// with the deduction guide +// +// template <class It, class H = std::hash<iter_value_t<It>>> +// unordered_set(It, It, H = H()) -> unordered_set<iter_value_t<It>, H>; +// +// Deducing the return type of the guide from the defining-type-id of MySet +// gives H = Hash, so Hash and, through its default template argument, Key are +// template parameters of f': +// +// template <class Key, class Hash = std::hash<Key>, class It> +// MySet(It, It, Hash) -> unordered_set<iter_value_t<It>, Hash>; +// +// and `MySet(first, last)` fails, as Key cannot be deduced. However, Key +// corresponds to `iter_value_t<It>` in the return type of the guide: deducing +// the template arguments of A from the return type of f gives +// Key = iter_value_t<It>, which we use as the default template argument of Key +// in f' (see orderFPrimeTemplateParameters for the resulting order): +// +// template <class It, class Key = iter_value_t<It>, +// class Hash = std::hash<Key>> +// MySet(It, It, Hash) -> unordered_set<iter_value_t<It>, Hash>; +// +// The result has an entry for each template parameter of A, which is null for +// those that don't get a synthesized default template argument. The synthesized +// default template arguments refer to the (non-deduced) template parameters of +// f. +static SmallVector<TemplateArgument> synthesizeDefaultArgumentsForFPrime( + Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate, + FunctionTemplateDecl *F, ArrayRef<TemplateArgument> AliasRhsTemplateArgs, + ArrayRef<TemplateArgument> FReturnTemplateArgs, + ArrayRef<DeducedTemplateArgument> DeduceResults, + ArrayRef<unsigned> AliasParamsInFPrime, SourceLocation Loc) { + TemplateParameterList *AliasParams = AliasTemplate->getTemplateParameters(); + TemplateParameterList *FParams = F->getTemplateParameters(); + SmallVector<TemplateArgument> Result(AliasParams->size()); + + auto NeedsDefaultArgument = [&](unsigned Index) { + NamedDecl *Param = AliasParams->getParam(Index); + return !Param->isTemplateParameterPack() && !hasDefaultArgument(Param); + }; + + if (llvm::none_of(AliasParamsInFPrime, NeedsDefaultArgument)) + return Result; + + // Deduce the template arguments of A from the return type of f, the reverse + // of the deduction of the template arguments of f from the defining-type-id + // of A. + sema::TemplateDeductionInfo Info(Loc, AliasParams->getDepth()); + SmallVector<DeducedTemplateArgument> Deduced(AliasParams->size()); + SemaRef.DeduceTemplateArguments(AliasParams, AliasRhsTemplateArgs, + FReturnTemplateArgs, Info, Deduced, + /*NumberOfArgumentsMustMatch=*/false); + + for (unsigned Index : AliasParamsInFPrime) { + if (!NeedsDefaultArgument(Index)) + continue; + const TemplateArgument &D = Deduced[Index]; + + if (D.isNull() || D.isPackExpansion()) + continue; + + NamedDecl *Param = AliasParams->getParam(Index); + + bool KindMatches = [&] { + switch (D.getKind()) { + case TemplateArgument::Type: + return isa<TemplateTypeParmDecl>(Param); + case TemplateArgument::Template: + return isa<TemplateTemplateParmDecl>(Param); + case TemplateArgument::Expression: + return isa<NonTypeTemplateParmDecl>(Param); + default: + return false; + } + }(); + + if (!KindMatches) + continue; + + // The deduced argument may only refer to the non-deduced template + // parameters of f. The deduced ones are replaced in f' by the template + // parameters of A they were deduced to, whose default template arguments + // may in turn refer to this template parameter. + llvm::SmallBitVector UsedFParams(FParams->size()); + SemaRef.MarkUsedTemplateParameters(D, /*OnlyDeduced=*/false, + FParams->getDepth(), UsedFParams); + if (llvm::any_of(UsedFParams.set_bits(), [&](unsigned FIndex) { + return !IsNonDeducedArgument(DeduceResults[FIndex]); + })) + continue; + Result[Index] = D; + } + return Result; +} + +// Determine the order of the template parameters of the deduction guide f' of +// the alias template A when some of them got a synthesized default template +// argument (see synthesizeDefaultArgumentsForFPrime). +// +// A default template argument can only refer to preceding template parameters. +// The order given by the standard (the template parameters of A, followed by +// the non-deduced template parameters of f) doesn't satisfy that for the +// synthesized default template arguments, which refer to non-deduced template +// parameters of f. Instead, order the template parameters of f' such that each +// of them follows the ones its default template argument, its type (for a +// non-type template parameter) and its type-constraint refer to, staying as +// close to the standard's order as possible. +// +// Returns false if there is no such order. +static bool orderFPrimeTemplateParameters( + Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate, + FunctionTemplateDecl *F, ArrayRef<DeducedTemplateArgument> DeduceResults, + ArrayRef<unsigned> AliasParamsInFPrime, + ArrayRef<unsigned> NonDeducedFParams, + ArrayRef<TemplateArgument> SynthesizedDefaultArgs, + SmallVectorImpl<FPrimeTemplateParamRef> &Order) { + TemplateParameterList *AliasParams = AliasTemplate->getTemplateParameters(); + TemplateParameterList *FParams = F->getTemplateParameters(); + + // The template parameters of f' in the standard's order, and the position + // of each template parameter of A / non-deduced template parameter of f in + // that list. + SmallVector<FPrimeTemplateParamRef> Params; + SmallVector<unsigned> AliasParamPos(AliasParams->size(), InvalidFPrimeIndex); + SmallVector<unsigned> FParamPos(FParams->size(), InvalidFPrimeIndex); + + for (unsigned Index : AliasParamsInFPrime) { + AliasParamPos[Index] = Params.size(); + Params.push_back( + {/*IsAliasParam=*/true, Index, llvm::SmallBitVector(FParams->size())}); + } + + for (unsigned Index : NonDeducedFParams) { + FParamPos[Index] = Params.size(); + Params.push_back( + {/*IsAliasParam=*/false, Index, llvm::SmallBitVector(FParams->size())}); + } + + unsigned NumParams = Params.size(); + + // Mark the template parameters (of the list containing Param, at the given + // depth) that the default template argument, the type and the + // type-constraint of Param refer to. + auto MarkReferencedTemplateParams = [&](NamedDecl *Param, unsigned Depth, + llvm::SmallBitVector &Used) { + if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) { + if (TTP->hasDefaultArgument()) + SemaRef.MarkUsedTemplateParameters( + TTP->getDefaultArgument().getArgument(), /*OnlyDeduced=*/false, + Depth, Used); + if (const TypeConstraint *TC = TTP->getTypeConstraint()) + if (const Expr *E = TC->getImmediatelyDeclaredConstraint()) + SemaRef.MarkUsedTemplateParameters(E, /*OnlyDeduced=*/false, Depth, + Used); + } else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(Param)) { + if (NTTP->hasDefaultArgument()) + SemaRef.MarkUsedTemplateParameters( + NTTP->getDefaultArgument().getArgument(), /*OnlyDeduced=*/false, + Depth, Used); + SemaRef.MarkUsedTemplateParameters(TemplateArgument(NTTP->getType()), + /*OnlyDeduced=*/false, Depth, Used); + } else { + auto *TTP = cast<TemplateTemplateParmDecl>(Param); + if (TTP->hasDefaultArgument()) + SemaRef.MarkUsedTemplateParameters( + TTP->getDefaultArgument().getArgument(), /*OnlyDeduced=*/false, + Depth, Used); + } + }; + + // Deps[I] holds the positions of the template parameters that must precede + // the I-th template parameter. + SmallVector<llvm::SmallBitVector> Deps(NumParams, + llvm::SmallBitVector(NumParams)); + for (auto [Pos, P] : llvm::enumerate(Params)) { + if (P.IsAliasParam) { + llvm::SmallBitVector UsedAliasParams(AliasParams->size()); + MarkReferencedTemplateParams(AliasParams->getParam(P.Index), + AliasParams->getDepth(), UsedAliasParams); + for (unsigned Index : UsedAliasParams.set_bits()) + if (Index != P.Index && AliasParamPos[Index] != InvalidFPrimeIndex) + Deps[Pos].set(AliasParamPos[Index]); + // The synthesized default template argument refers to non-deduced + // template parameters of f. + if (!SynthesizedDefaultArgs[P.Index].isNull()) { + llvm::SmallBitVector UsedFParams(FParams->size()); + SemaRef.MarkUsedTemplateParameters(SynthesizedDefaultArgs[P.Index], + /*OnlyDeduced=*/false, + FParams->getDepth(), UsedFParams); + for (unsigned Index : UsedFParams.set_bits()) { + assert(FParamPos[Index] != InvalidFPrimeIndex && + "synthesized default argument refers to a deduced parameter"); + Deps[Pos].set(FParamPos[Index]); + } + } + continue; + } + + llvm::SmallBitVector UsedFParams(FParams->size()); + MarkReferencedTemplateParams(FParams->getParam(P.Index), + FParams->getDepth(), UsedFParams); + for (unsigned Index : UsedFParams.set_bits()) { + if (Index == P.Index) + continue; + if (FParamPos[Index] != InvalidFPrimeIndex) { + Deps[Pos].set(FParamPos[Index]); + continue; + } + // A deduced template parameter of f, which is replaced in f' by the + // template parameters of A that its deduced argument refers to. + P.DeducedFParamsUsed.set(Index); + const TemplateArgument &DeducedArg = DeduceResults[Index]; + llvm::SmallBitVector UsedAliasParams(AliasParams->size()); + SemaRef.MarkUsedTemplateParameters(DeducedArg, /*OnlyDeduced=*/false, + AliasParams->getDepth(), + UsedAliasParams); + for (unsigned AliasIndex : UsedAliasParams.set_bits()) + if (AliasParamPos[AliasIndex] != InvalidFPrimeIndex) + Deps[Pos].set(AliasParamPos[AliasIndex]); + } + } + + // Repeatedly pick the first template parameter all of whose dependencies + // have been placed. + llvm::SmallBitVector Placed(NumParams); + while (Order.size() < NumParams) { + unsigned Next = NumParams; + for (unsigned Pos = 0; Pos != NumParams && Next == NumParams; ++Pos) + if (!Placed[Pos] && !Deps[Pos].test(Placed)) + Next = Pos; + if (Next == NumParams) // The dependencies are circular. + return false; + Placed.set(Next); + Order.push_back(std::move(Params[Next])); + } + return true; +} + // Build deduction guides for a type alias template from the given underlying // source deduction guide. CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( @@ -1349,26 +1643,59 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( SmallVector<TemplateArgument> TemplateArgsForBuildingFPrime( F->getTemplateParameters()->size()); + TemplateParameterList *AliasParams = AliasTemplate->getTemplateParameters(); + TemplateParameterList *FParams = F->getTemplateParameters(); + // Create a template parameter list for the synthesized deduction guide f'. // // C++ [over.match.class.deduct]p3.2: // If f is a function template, f' is a function template whose template // parameter list consists of all the template parameters of A // (including their default template arguments) that appear in the above - // deductions or (recursively) in their default template arguments + // deductions or (recursively) in their default template arguments, + // followed by the template parameters of f that were not deduced + // (including their default template arguments) SmallVector<NamedDecl *> FPrimeTemplateParams; // Store template arguments that refer to the newly-created template // parameters, used for building `TemplateArgsForBuildingFPrime`. SmallVector<TemplateArgument, 16> TransformedDeducedAliasArgs( - AliasTemplate->getTemplateParameters()->size()); + AliasParams->size()); + // The index in f' of the template parameters of A, and of the non-deduced + // template parameters of f, that appear in f'. + SmallVector<unsigned> AliasParamFPrimeIndex(AliasParams->size(), + InvalidFPrimeIndex); + SmallVector<unsigned> FParamFPrimeIndex(FParams->size(), InvalidFPrimeIndex); + + // Template parameters of A that appear in f' without a default template + // argument, and that cannot be deduced from the function parameters of f', + // get a default template argument synthesized from the return type of f. + SmallVector<TemplateArgument> SynthesizedDefaultArgs = + synthesizeDefaultArgumentsForFPrime( + SemaRef, AliasTemplate, F, AliasRhsTemplateArgs, FReturnTemplateArgs, + DeduceResults, DeducedAliasTemplateParams, Loc); + // Those refer to template parameters of f, which the standard's order places + // after the template parameters of A; reorder the template parameters of f' + // so that default template arguments only refer to preceding template + // parameters. If that is not possible, don't synthesize any. + SmallVector<FPrimeTemplateParamRef> FPrimeParamOrder; + if (llvm::any_of(SynthesizedDefaultArgs, + [](const TemplateArgument &TA) { return !TA.isNull(); }) && + !orderFPrimeTemplateParameters( + SemaRef, AliasTemplate, F, DeduceResults, DeducedAliasTemplateParams, + NonDeducedTemplateParamsInFIndex, SynthesizedDefaultArgs, + FPrimeParamOrder)) { + llvm::fill(SynthesizedDefaultArgs, TemplateArgument()); + FPrimeParamOrder.clear(); + } + // We might be already within a pack expansion, but rewriting template // parameters is independent of that. (We may or may not expand new packs // when rewriting. So clear the state) Sema::ArgPackSubstIndexRAII PackSubstReset(SemaRef, std::nullopt); - for (unsigned AliasTemplateParamIdx : DeducedAliasTemplateParams) { - auto *TP = - AliasTemplate->getTemplateParameters()->getParam(AliasTemplateParamIdx); + // Add the template parameter of A at the given index to f'. + auto AddAliasTemplateParam = [&](unsigned AliasTemplateParamIdx) -> bool { + auto *TP = AliasParams->getParam(AliasTemplateParamIdx); // Rebuild any internal references to earlier parameters and reindex as // we go. MultiLevelTemplateArgumentList Args; @@ -1377,13 +1704,31 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( NamedDecl *NewParam = transformTemplateParameter( SemaRef, AliasTemplate->getDeclContext(), TP, Args, /*NewIndex=*/FPrimeTemplateParams.size(), getDepthAndIndex(TP).first); + if (const TemplateArgument &Default = + SynthesizedDefaultArgs[AliasTemplateParamIdx]; + !Default.isNull()) { + // The synthesized default template argument refers to template + // parameters of f; rewrite it in terms of the corresponding + // (already created) template parameters of f'. + QualType NTTPType; + if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(NewParam)) + NTTPType = NTTP->getType(); + MultiLevelTemplateArgumentList FArgs; + FArgs.setKind(TemplateSubstitutionKind::Rewrite); + FArgs.addOuterTemplateArguments(TemplateArgsForBuildingFPrime); + TemplateArgumentLoc Output; + if (SemaRef.SubstTemplateArgument( + SemaRef.getTrivialTemplateArgumentLoc(Default, NTTPType, Loc), + FArgs, Output, Loc, NewParam->getDeclName())) + return false; + setDefaultArgument(Context, NewParam, Output); + } + AliasParamFPrimeIndex[AliasTemplateParamIdx] = FPrimeTemplateParams.size(); FPrimeTemplateParams.push_back(NewParam); - - TemplateArgument NewTemplateArgument = + TransformedDeducedAliasArgs[AliasTemplateParamIdx] = Context.getInjectedTemplateArg(NewParam); - TransformedDeducedAliasArgs[AliasTemplateParamIdx] = NewTemplateArgument; - } - unsigned FirstUndeducedParamIdx = FPrimeTemplateParams.size(); + return true; + }; // To form a deduction guide f' from f, we leverage clang's instantiation // mechanism, we construct a template argument list where the template @@ -1400,21 +1745,21 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( // // Note: the non-deduced template arguments of `f` might refer to arguments // deduced in 1), as in a type constraint. - MultiLevelTemplateArgumentList Args; - Args.setKind(TemplateSubstitutionKind::Rewrite); - Args.addOuterTemplateArguments(TransformedDeducedAliasArgs); - for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) { + // + // Substitute the template parameters of f' into the deduced template + // argument for the template parameter of f at the given index (1). The + // template parameters of A it refers to must have been added to f' already. + auto SubstDeducedTemplateArg = [&](unsigned Index) -> bool { const auto &D = DeduceResults[Index]; - auto *TP = F->getTemplateParameters()->getParam(Index); - if (IsNonDeducedArgument(D)) { - // 2): Non-deduced template parameters would be substituted later. - continue; - } + auto *TP = FParams->getParam(Index); + MultiLevelTemplateArgumentList Args; + Args.setKind(TemplateSubstitutionKind::Rewrite); + Args.addOuterTemplateArguments(TransformedDeducedAliasArgs); TemplateArgumentLoc Input = SemaRef.getTrivialTemplateArgumentLoc(D, QualType(), SourceLocation{}); TemplateArgumentListInfo Output; if (SemaRef.SubstTemplateArguments(Input, Args, Output)) - return nullptr; + return false; assert(TemplateArgsForBuildingFPrime[Index].isNull() && "InstantiatedArgs must be null before setting"); // CheckTemplateArgument is necessary for NTTP initializations. @@ -1428,7 +1773,7 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( TP, TA, F, F->getLocation(), F->getLocation(), /*ArgumentPackIndex=*/-1, CTAI, Sema::CheckTemplateArgumentKind::CTAK_Specified)) - return nullptr; + return false; if (Input.getArgument().getKind() == TemplateArgument::Pack) { // We will substitute the non-deduced template arguments with these // transformed (unpacked at this point) arguments, where that substitution @@ -1439,13 +1784,12 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( assert(Output.arguments().size() == 1); TemplateArgsForBuildingFPrime[Index] = CTAI.SugaredConverted[0]; } - } + return true; + }; - // Case 2) - // ...followed by the template parameters of f that were not deduced - // (including their default template arguments) - for (unsigned FTemplateParamIdx : NonDeducedTemplateParamsInFIndex) { - auto *TP = F->getTemplateParameters()->getParam(FTemplateParamIdx); + // Add the non-deduced template parameter of f at the given index to f' (2). + auto AddFTemplateParam = [&](unsigned FTemplateParamIdx) { + auto *TP = FParams->getParam(FTemplateParamIdx); MultiLevelTemplateArgumentList Args; Args.setKind(TemplateSubstitutionKind::Rewrite); // We take a shortcut here, it is ok to reuse the @@ -1454,12 +1798,48 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( NamedDecl *NewParam = transformTemplateParameter( SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size(), getDepthAndIndex(TP).first); + FParamFPrimeIndex[FTemplateParamIdx] = FPrimeTemplateParams.size(); FPrimeTemplateParams.push_back(NewParam); assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() && "The argument must be null before setting"); TemplateArgsForBuildingFPrime[FTemplateParamIdx] = Context.getInjectedTemplateArg(NewParam); + }; + + if (FPrimeParamOrder.empty()) { + // The standard's order: the template parameters of A that appear in the + // deductions, followed by the non-deduced template parameters of f. + for (unsigned AliasTemplateParamIdx : DeducedAliasTemplateParams) + if (!AddAliasTemplateParam(AliasTemplateParamIdx)) + return nullptr; + for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) + if (!IsNonDeducedArgument(DeduceResults[Index]) && + !SubstDeducedTemplateArg(Index)) + return nullptr; + for (unsigned FTemplateParamIdx : NonDeducedTemplateParamsInFIndex) + AddFTemplateParam(FTemplateParamIdx); + } else { + for (const FPrimeTemplateParamRef &P : FPrimeParamOrder) { + if (P.IsAliasParam) { + if (!AddAliasTemplateParam(P.Index)) + return nullptr; + continue; + } + // The template parameters of A that the deduced template arguments + // referred to by this template parameter of f refer to have been added + // already, so we can substitute them now. + for (unsigned Index : P.DeducedFParamsUsed.set_bits()) + if (TemplateArgsForBuildingFPrime[Index].isNull() && + !SubstDeducedTemplateArg(Index)) + return nullptr; + AddFTemplateParam(P.Index); + } + for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) + if (!IsNonDeducedArgument(DeduceResults[Index]) && + TemplateArgsForBuildingFPrime[Index].isNull() && + !SubstDeducedTemplateArg(Index)) + return nullptr; } auto *TemplateArgListForBuildingFPrime = @@ -1472,9 +1852,9 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( Expr *IsDeducible = buildIsDeducibleConstraint( SemaRef, AliasTemplate, FPrime->getReturnType(), FPrimeTemplateParams); - Expr *RequiresClause = - buildAssociatedConstraints(SemaRef, F, AliasTemplate, DeduceResults, - FirstUndeducedParamIdx, IsDeducible); + Expr *RequiresClause = buildAssociatedConstraints( + SemaRef, F, AliasTemplate, DeduceResults, AliasParamFPrimeIndex, + FParamFPrimeIndex, IsDeducible); TemplateParameterList *FPrimeTemplateParamList = nullptr; if (!FPrimeTemplateParams.empty()) diff --git a/clang/test/AST/ast-dump-ctad-alias.cpp b/clang/test/AST/ast-dump-ctad-alias.cpp index 4ca84dc2d1828..2704bab0910bc 100644 --- a/clang/test/AST/ast-dump-ctad-alias.cpp +++ b/clang/test/AST/ast-dump-ctad-alias.cpp @@ -202,3 +202,33 @@ void foo() { // CHECK-NEXT: | | | | `-TemplateTypeParm {{.*}} 'U' } // namespace GH124715 + +// A template parameter of the alias template that cannot be deduced from the +// constructor arguments gets a default template argument deduced from the +// return type of the underlying deduction guide ('Key' below), and the template +// parameters of the synthesized guide are ordered so that default template +// arguments only refer to preceding ones ('It' comes first). +namespace synthesized_default_args { +template <class T> struct hash {}; +template <class It> struct iter_traits { using value_type = typename It::value_type; }; +struct Iter { using value_type = int; }; +template <class Key, class Hash = hash<Key>> struct Set { template <class It> Set(It, It); }; +template <class It, class Hash = hash<typename iter_traits<It>::value_type>> +Set(It, It, Hash = Hash()) -> Set<typename iter_traits<It>::value_type, Hash>; +template <class Key, class Hash = hash<Key>> using MySet = Set<Key, Hash>; +void f(Iter b, Iter e) { MySet s(b, e); } + +// CHECK: <deduction guide for MySet> 'auto (synthesized_default_args::Set<Key, Hash>) -> synthesized_default_args::Set<Key, Hash>' +// CHECK: `-FunctionTemplateDecl {{.*}} <deduction guide for MySet> +// CHECK-NEXT: |-TemplateTypeParmDecl {{.*}} class depth 0 index 0 It +// CHECK-NEXT: |-TemplateTypeParmDecl {{.*}} class depth 0 index 1 Key +// CHECK-NEXT: | `-TemplateArgument type 'typename iter_traits<It>::value_type':'synthesized_default_args::iter_traits<type-parameter-0-0>::value_type' +// CHECK-NEXT: | `-DependentNameType {{.*}} 'typename iter_traits<It>::value_type' dependent +// CHECK-NEXT: |-TemplateTypeParmDecl {{.*}} class depth 0 index 2 Hash +// CHECK-NEXT: | `-TemplateArgument type 'hash<Key>':'synthesized_default_args::hash<type-parameter-0-1>' +// CHECK: |-CXXDeductionGuideDecl {{.*}} <deduction guide for MySet> 'auto (It, It, Hash) -> Set<typename iter_traits<It>::value_type, Hash>' +// CHECK-NEXT: | |-ParmVarDecl {{.*}} 'It' +// CHECK-NEXT: | |-ParmVarDecl {{.*}} 'It' +// CHECK-NEXT: | `-ParmVarDecl {{.*}} 'Hash' +// CHECK-NEXT: `-CXXDeductionGuideDecl {{.*}} used <deduction guide for MySet> 'auto (synthesized_default_args::Iter, synthesized_default_args::Iter, synthesized_default_args::hash<int>) -> Set<typename iter_traits<synthesized_default_args::Iter>::value_type, synthesized_default_args::hash<int>>' implicit_instantiation +} // namespace synthesized_default_args diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp index e0f59be663b2f..fe7012e1a87ab 100644 --- a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp +++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp @@ -629,3 +629,105 @@ template <typename T> using S3 = S2<T>; // expected-note {{candidate function no // expected-note {{cannot deduce template arguments for 'GH190517::S3' from 'GH190517::S1<char>'}} S3 foo(42); // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'S3'}} } + +// Template parameters of the alias template that appear in a synthesized +// deduction guide only through the default template arguments of other +// template parameters cannot be deduced from the constructor arguments. They +// get a default template argument deduced from the return type of the +// underlying deduction guide instead, and the template parameters of the +// synthesized guide are ordered so that default template arguments only refer +// to preceding ones. +namespace synthesized_default_args { +template <class T> struct hash {}; +template <class T> struct alloc {}; +template <class It> struct iter_traits { using value_type = typename It::value_type; }; +struct Iter { using value_type = int; }; + +template <class Key, class Hash = hash<Key>, class Alloc = alloc<Key>> +struct Set { + Set(); + template <class It> Set(It, It); + template <class It> Set(It, It, Hash); +}; +template <class It, + class Hash = hash<typename iter_traits<It>::value_type>, + class Alloc = alloc<typename iter_traits<It>::value_type>> +Set(It, It, Hash = Hash(), Alloc = Alloc()) + -> Set<typename iter_traits<It>::value_type, Hash, Alloc>; + +// Like std::unordered_set: the alias merely renames the class template. +template <class Key, class Hash = hash<Key>, class Alloc = alloc<Key>> +using MySet = Set<Key, Hash, Alloc>; +// The alias has fewer template parameters than the class template. +template <class Key, class Hash = hash<Key>> +using MySet2 = Set<Key, Hash>; +// The alias has a different default template argument, which wins. +template <class Key, class Hash = hash<Key*>, class Alloc = alloc<Key>> +using MySet3 = Set<Key, Hash, Alloc>; // #MySet3 + +void f(Iter b, Iter e) { + MySet s1(b, e); + static_assert(__is_same(decltype(s1), Set<int, hash<int>, alloc<int>>)); + MySet s2(b, e, hash<long>()); + static_assert(__is_same(decltype(s2), Set<int, hash<long>, alloc<int>>)); + MySet2 s3(b, e); + static_assert(__is_same(decltype(s3), Set<int, hash<int>, alloc<int>>)); + MySet3 s4(b, e); + static_assert(__is_same(decltype(s4), Set<int, hash<int*>, alloc<int>>)); + MySet s5 = s1; + static_assert(__is_same(decltype(s5), decltype(s1))); + + // The non-deduced template parameter 'It' of the underlying guide now comes + // first, followed by 'Key' with its synthesized default template argument. + MySet3 s6(b, e, 1, 2, 3); // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'MySet3'}} + // expected-note@#MySet3 {{implicit deduction guide declared as 'template <class It, class Key = typename iter_traits<It>::value_type, class Hash = hash<Key *>, class Alloc = alloc<Key>> requires __is_deducible(synthesized_default_args::MySet3, Set<typename iter_traits<It>::value_type, Hash, Alloc>) MySet3(It, It, Hash, Alloc) -> Set<typename iter_traits<It>::value_type, Hash, Alloc>'}} + // expected-note@#MySet3 4 {{implicit deduction guide declared as}} + // expected-note@#MySet3 {{requires at most 4 arguments, but 5 were provided}} + // expected-note@#MySet3 {{requires 3 arguments, but 5 were provided}} + // expected-note@#MySet3 {{requires 2 arguments, but 5 were provided}} + // expected-note@#MySet3 {{requires 1 argument, but 5 were provided}} + // expected-note@#MySet3 {{requires 0 arguments, but 5 were provided}} +} + +// A template parameter of the alias that is deducible from some constructor +// arguments only. +template <class T, class A = alloc<T>> struct Vec { + template <class It> Vec(It, It); + template <class It> Vec(It, It, A); +}; +template <class It, class A = alloc<typename iter_traits<It>::value_type>> +Vec(It, It, A = A()) -> Vec<typename iter_traits<It>::value_type, A>; +template <class T> using MyVec = Vec<T, hash<T>>; + +void g(Iter b, Iter e) { + MyVec v1(b, e); + static_assert(__is_same(decltype(v1), Vec<int, hash<int>>)); + MyVec v2(b, e, hash<int>()); + static_assert(__is_same(decltype(v2), Vec<int, hash<int>>)); +} + +// The underlying guide is constrained, and the alias is a member of a class +// template. +template <class T> concept Any = true; +template <class Key, class Hash = hash<Key>> struct CSet { + CSet(); + template <class It> CSet(It, It); +}; +template <class It, class Hash = hash<typename iter_traits<It>::value_type>> + requires Any<It> && Any<Hash> +CSet(It, It, Hash = Hash()) -> CSet<typename iter_traits<It>::value_type, Hash>; + +template <class U> struct Outer { + template <class Key, class Hash = hash<Key>> using MyCSet = CSet<Key, Hash>; +}; + +void h(Iter b, Iter e) { + Outer<long>::MyCSet s1(b, e); + static_assert(__is_same(decltype(s1), CSet<int, hash<int>>)); +} +template <class U> void h2(Iter b, Iter e) { + typename Outer<U>::MyCSet s(b, e); + static_assert(__is_same(decltype(s), CSet<int, hash<int>>)); +} +template void h2<char>(Iter, Iter); +} // namespace synthesized_default_args >From 4273ef2625e800180ff9a722b7b4b96fc9f7e904 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Thu, 3 Sep 2026 22:36:41 +0300 Subject: [PATCH 08/12] chore: unwanted format --- clang/docs/ReleaseNotes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 21578b5eb87e1..6cded152a827b 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -73,7 +73,7 @@ features cannot lower the translation-unit ABI level; - On SPARC, a `_Complex` value with an integer element type is now passed and returned packed into the one or two integer registers it fits in, matching GCC. Clang previously passed such a value indirectly and returned it with one part - per register. + per register. `-fclang-abi-compat=23` restores the previous behavior. (#GH212340) - On SPARC64, a `_Complex char` or `_Complex short` is now @@ -119,7 +119,7 @@ features cannot lower the translation-unit ABI level; - `CompletionString.availability` now returns instances of `AvailabilityKind`. As a result, the `__str__` representation of its return values changed. - Like other libclang enums, it now follows the `CompletionChunkKind.VARIANT_NAME` scheme instead of `VariantName`. + Like other libclang enums, it now follows the `CompletionChunkKind.VARIANT_NAME` scheme instead of `VariantName`. ### OpenCL Potentially Breaking Changes @@ -498,9 +498,9 @@ features cannot lower the translation-unit ABI level; producing a spurious "no matching function" error with no candidate notes. (#GH210822) -- Fixed a crash when module directive export module foo not following a +- Fixed a crash when module directive export module foo not following a semicolon and there are no rest pp-tokens in current module file. (#GH187771) - + - Fixed a crash when a lambda parameter pack was given a default argument that is a pack expansion referencing an enclosing function's parameter pack (e.g. `[](Types... = args...) {}`). Clang now diagnoses the illegal default >From 3d8a41a80165f87b7e7a0e8dfea3386d7a06e233 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Fri, 4 Sep 2026 03:21:15 +0300 Subject: [PATCH 09/12] Fix crash in class template argument deduction with alias templates --- clang/docs/ReleaseNotes.md | 8 +++ clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 57 +++++++++++++++---- clang/test/SemaCXX/cxx20-ctad-type-alias.cpp | 47 +++++++++++++++ 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 6cded152a827b..26cb86cc7dc92 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -484,6 +484,14 @@ features cannot lower the translation-unit ABI level; guide (here `Key` becomes the iterator's value type), instead of being undeducible. +- Fixed a crash in class template argument deduction through an alias template + when the alias fixes a non-type template parameter of the underlying template + to a constant (e.g. `template <class T> using A = S<T, false>;`) and a + non-deduced template parameter of a constructor or deduction guide refers to + it in its type, such as `std::enable_if_t<!B, int> = 0`. If that type becomes + invalid with the constant substituted, the deduction guide is now silently + not synthesized for the alias instead of crashing, matching GCC. + - Fixed a bug where top-level CV qualifiers (such as ``const``) were dropped from pointers modified by Microsoft pointer attributes (like ``__ptr32`` and ``__ptr64``) and WebAssembly's ``__funcref``. - Fixed an issue where we tried to compare invalid NTTPs for variable declarations, which ended up in hitting an assertion with a constrained non-plain-auto NTTP, which we don't quite implement yet. (#GH208658) diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp index 05b99c9374148..d85c84562a082 100644 --- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp +++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp @@ -337,6 +337,8 @@ transformTemplateParam(Sema &SemaRef, DeclContext *DC, return NewTTP; } +// Transform a given non-type template parameter `TTP`. Returns null if its type +// becomes invalid after substitution. NonTypeTemplateParmDecl * transformTemplateParam(Sema &SemaRef, DeclContext *DC, NonTypeTemplateParmDecl *TTP, unsigned NewDepth, @@ -351,11 +353,13 @@ transformTemplateParam(Sema &SemaRef, DeclContext *DC, TypeSourceInfo *NewTSI = SemaRef.SubstType(TTP->getExpansionTypeSourceInfo(I), Args, TTP->getLocation(), TTP->getDeclName()); - assert(NewTSI); + if (!NewTSI) + return nullptr; QualType NewT = SemaRef.CheckNonTypeTemplateParameterType(NewTSI, TTP->getLocation()); - assert(!NewT.isNull()); + if (NewT.isNull()) + return nullptr; ExpandedTypeSourceInfos[I] = NewTSI; ExpandedTypes[I] = NewT; @@ -367,11 +371,13 @@ transformTemplateParam(Sema &SemaRef, DeclContext *DC, } else { TypeSourceInfo *NewTSI = SemaRef.SubstType( TTP->getTypeSourceInfo(), Args, TTP->getLocation(), TTP->getDeclName()); - assert(NewTSI); + if (!NewTSI) + return nullptr; QualType NewT = SemaRef.CheckNonTypeTemplateParameterType(NewTSI, TTP->getLocation()); - assert(!NewT.isNull()); + if (NewT.isNull()) + return nullptr; NewTTP = NonTypeTemplateParmDecl::Create( SemaRef.Context, DC, TTP->getBeginLoc(), TTP->getLocation(), NewDepth, @@ -1642,6 +1648,16 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( // All template arguments null by default. SmallVector<TemplateArgument> TemplateArgsForBuildingFPrime( F->getTemplateParameters()->size()); + // The same template arguments, but with the deduced non-type template + // arguments as (not yet converted) expressions, for rewriting the template + // parameters of f in terms of those of f'. The rewrite requires expressions, + // while instantiating f requires converted template arguments, e.g. a + // template parameter `bool B` of f deduced as `false` from the alias must be + // rewritten as the expression `false` in the type of a template parameter + // `std::enable_if_t<!B, int> = 0` of f, but instantiated as the integral + // value. + SmallVector<TemplateArgument> TemplateArgsForRewritingFPrime( + F->getTemplateParameters()->size()); TemplateParameterList *AliasParams = AliasTemplate->getTemplateParameters(); TemplateParameterList *FParams = F->getTemplateParameters(); @@ -1704,6 +1720,8 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( NamedDecl *NewParam = transformTemplateParameter( SemaRef, AliasTemplate->getDeclContext(), TP, Args, /*NewIndex=*/FPrimeTemplateParams.size(), getDepthAndIndex(TP).first); + if (!NewParam) + return false; if (const TemplateArgument &Default = SynthesizedDefaultArgs[AliasTemplateParamIdx]; !Default.isNull()) { @@ -1715,7 +1733,7 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( NTTPType = NTTP->getType(); MultiLevelTemplateArgumentList FArgs; FArgs.setKind(TemplateSubstitutionKind::Rewrite); - FArgs.addOuterTemplateArguments(TemplateArgsForBuildingFPrime); + FArgs.addOuterTemplateArguments(TemplateArgsForRewritingFPrime); TemplateArgumentLoc Output; if (SemaRef.SubstTemplateArgument( SemaRef.getTrivialTemplateArgumentLoc(Default, NTTPType, Loc), @@ -1774,30 +1792,42 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( /*ArgumentPackIndex=*/-1, CTAI, Sema::CheckTemplateArgumentKind::CTAK_Specified)) return false; + SmallVector<TemplateArgument> OutputArgs; + for (const TemplateArgumentLoc &TA : Output.arguments()) + OutputArgs.push_back(TA.getArgument()); if (Input.getArgument().getKind() == TemplateArgument::Pack) { // We will substitute the non-deduced template arguments with these // transformed (unpacked at this point) arguments, where that substitution // requires a pack for the corresponding parameter packs. TemplateArgsForBuildingFPrime[Index] = TemplateArgument::CreatePackCopy(Context, CTAI.SugaredConverted); + TemplateArgsForRewritingFPrime[Index] = + TemplateArgument::CreatePackCopy(Context, OutputArgs); } else { assert(Output.arguments().size() == 1); TemplateArgsForBuildingFPrime[Index] = CTAI.SugaredConverted[0]; + TemplateArgsForRewritingFPrime[Index] = OutputArgs[0]; } return true; }; // Add the non-deduced template parameter of f at the given index to f' (2). - auto AddFTemplateParam = [&](unsigned FTemplateParamIdx) { + auto AddFTemplateParam = [&](unsigned FTemplateParamIdx) -> bool { auto *TP = FParams->getParam(FTemplateParamIdx); MultiLevelTemplateArgumentList Args; Args.setKind(TemplateSubstitutionKind::Rewrite); - // We take a shortcut here, it is ok to reuse the - // TemplateArgsForBuildingFPrime. - Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime); + Args.addOuterTemplateArguments(TemplateArgsForRewritingFPrime); + // Substituting the deduced template arguments into the template parameter + // may fail, e.g. for a template parameter `std::enable_if_t<!B, int> = 0` + // of f whose template parameter B was deduced as `true` from the alias. + // Then f' can't be formed; it is not viable, but forming it is not an + // error either. Don't diagnose the failure and don't form f'. + Sema::SFINAETrap Trap(SemaRef); NamedDecl *NewParam = transformTemplateParameter( SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size(), getDepthAndIndex(TP).first); + if (!NewParam || Trap.hasErrorOccurred()) + return false; FParamFPrimeIndex[FTemplateParamIdx] = FPrimeTemplateParams.size(); FPrimeTemplateParams.push_back(NewParam); @@ -1805,6 +1835,9 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( "The argument must be null before setting"); TemplateArgsForBuildingFPrime[FTemplateParamIdx] = Context.getInjectedTemplateArg(NewParam); + TemplateArgsForRewritingFPrime[FTemplateParamIdx] = + TemplateArgsForBuildingFPrime[FTemplateParamIdx]; + return true; }; if (FPrimeParamOrder.empty()) { @@ -1818,7 +1851,8 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( !SubstDeducedTemplateArg(Index)) return nullptr; for (unsigned FTemplateParamIdx : NonDeducedTemplateParamsInFIndex) - AddFTemplateParam(FTemplateParamIdx); + if (!AddFTemplateParam(FTemplateParamIdx)) + return nullptr; } else { for (const FPrimeTemplateParamRef &P : FPrimeParamOrder) { if (P.IsAliasParam) { @@ -1833,7 +1867,8 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( if (TemplateArgsForBuildingFPrime[Index].isNull() && !SubstDeducedTemplateArg(Index)) return nullptr; - AddFTemplateParam(P.Index); + if (!AddFTemplateParam(P.Index)) + return nullptr; } for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) if (!IsNonDeducedArgument(DeduceResults[Index]) && diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp index fe7012e1a87ab..f9d190f97abec 100644 --- a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp +++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp @@ -731,3 +731,50 @@ template <class U> void h2(Iter b, Iter e) { } template void h2<char>(Iter, Iter); } // namespace synthesized_default_args + +namespace nttp_deduced_from_alias_in_nondeduced_param_type { +// The template parameter B of the deduction guide of basic_fn is deduced as +// the constant `false` from the alias fn_ref, while the type of the +// non-deduced template parameter `enable_if_t<!bool_constant<B>::value, int>` +// refers to it; this used to crash when rewriting that type for the deduction +// guide of fn_ref. +template <bool B> struct bool_constant { static constexpr bool value = B; }; +using false_type = bool_constant<false>; +using true_type = bool_constant<true>; +template <bool, class T = void> struct enable_if {}; +template <class T> struct enable_if<true, T> { using type = T; }; +template <bool B, class T = void> using enable_if_t = typename enable_if<B, T>::type; + +template <class T, class C> struct function { + template <class U, enable_if_t<!C::value, int> = 0> + function(U) noexcept {} +}; + +template <class T> function(T) -> function<T, false_type>; + +template <class T, bool B> using basic_fn = function<T, bool_constant<B>>; + +template <class T> using fn_ref = basic_fn<T, false>; +template <class T> using fn_disabled = basic_fn<T, true>; +// expected-note@-1 {{candidate template ignored: could not match 'nttp_deduced_from_alias_in_nondeduced_param_type::function<T, bool_constant<true>>' against 'int'}} +// expected-note@-2 {{implicit deduction guide declared as 'template <class T> requires __is_deducible(nttp_deduced_from_alias_in_nondeduced_param_type::basic_fn, nttp_deduced_from_alias_in_nondeduced_param_type::function<T, bool_constant<true>>) && __is_deducible(nttp_deduced_from_alias_in_nondeduced_param_type::fn_disabled, nttp_deduced_from_alias_in_nondeduced_param_type::function<T, bool_constant<true>>) fn_disabled(nttp_deduced_from_alias_in_nondeduced_param_type::function<T, bool_constant<true>>) -> nttp_deduced_from_alias_in_nondeduced_param_type::function<T, bool_constant<true>>'}} +// expected-note@-3 {{candidate template ignored: constraints not satisfied [with T = int]}} +// expected-note@-4 {{cannot deduce template arguments for 'nttp_deduced_from_alias_in_nondeduced_param_type::fn_disabled' from 'function<int, false_type>' (aka 'function<int, bool_constant<false>>')}} +// expected-note@-5 {{implicit deduction guide declared as 'template <class T> requires __is_deducible(nttp_deduced_from_alias_in_nondeduced_param_type::basic_fn, function<T, false_type>) && __is_deducible(nttp_deduced_from_alias_in_nondeduced_param_type::fn_disabled, function<T, false_type>) fn_disabled(T) -> function<T, false_type>'}} + +fn_ref f = 0; +static_assert(__is_same(decltype(f), function<int, false_type>)); + +// The deduction guide derived from the constructor is not formed, as +// substituting B = true into `enable_if_t<!bool_constant<B>::value, int>` fails. +fn_disabled g = 0; // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'fn_disabled'}} + +template <class T, int N> struct Arr { + template <class U, int M = N, enable_if_t<(M > 0), int> = 0> + Arr(T (&)[M], U = {}) {} +}; +template <class T> using Arr3 = Arr<T, 3>; +int arr3[3]; +Arr3 a3(arr3, 0); +static_assert(__is_same(decltype(a3), Arr<int, 3>)); +} // namespace nttp_deduced_from_alias_in_nondeduced_param_type >From fe7155caedb1d870842dc89324e5294c309b1080 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Fri, 4 Sep 2026 03:29:58 +0300 Subject: [PATCH 10/12] chore: fix style --- clang/test/AST/ast-dump-ctad-alias.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/clang/test/AST/ast-dump-ctad-alias.cpp b/clang/test/AST/ast-dump-ctad-alias.cpp index 2704bab0910bc..4454ed0e15ff7 100644 --- a/clang/test/AST/ast-dump-ctad-alias.cpp +++ b/clang/test/AST/ast-dump-ctad-alias.cpp @@ -209,12 +209,22 @@ void foo() { // parameters of the synthesized guide are ordered so that default template // arguments only refer to preceding ones ('It' comes first). namespace synthesized_default_args { + template <class T> struct hash {}; -template <class It> struct iter_traits { using value_type = typename It::value_type; }; + +template <class It> struct iter_traits { + using value_type = typename It::value_type; +}; + struct Iter { using value_type = int; }; -template <class Key, class Hash = hash<Key>> struct Set { template <class It> Set(It, It); }; + +template <class Key, class Hash = hash<Key>> struct Set { + template <class It> Set(It, It); +}; + template <class It, class Hash = hash<typename iter_traits<It>::value_type>> Set(It, It, Hash = Hash()) -> Set<typename iter_traits<It>::value_type, Hash>; + template <class Key, class Hash = hash<Key>> using MySet = Set<Key, Hash>; void f(Iter b, Iter e) { MySet s(b, e); } >From edb7c7053536168574efb702658ddab12b39d990 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Fri, 4 Sep 2026 04:33:39 +0300 Subject: [PATCH 11/12] Fix crash in class template argument deduction with lambda closure types in deduction guides --- clang/docs/ReleaseNotes.md | 7 ++++ clang/lib/Sema/SemaLambda.cpp | 8 +++++ clang/lib/Sema/SemaTemplateInstantiate.cpp | 10 ++++++ clang/test/AST/ByteCode/cxx2a.cpp | 10 ++++-- .../cxx20-ctad-lambda-in-deduction-guide.cpp | 35 +++++++++++++++++++ clang/test/SemaCXX/cxx20-ctad-type-alias.cpp | 29 +++++++++++++++ 6 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 clang/test/CodeGenCXX/cxx20-ctad-lambda-in-deduction-guide.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 26cb86cc7dc92..febbe7353c2f0 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -492,6 +492,13 @@ features cannot lower the translation-unit ABI level; invalid with the constant substituted, the deduction guide is now silently not synthesized for the alias instead of crashing, matching GCC. +- Fixed a crash in class template argument deduction when the deduced type + contains the closure type of a lambda from the right-hand side of an alias + template (`template <class T> using A = S<T, decltype([](T) {})>;`) or from + the return type of a user-written deduction guide. The lambda is now + instantiated along with the deduction guide, and its closure type is created + in the scope enclosing the guide so that it can be mangled, matching GCC. + - Fixed a bug where top-level CV qualifiers (such as ``const``) were dropped from pointers modified by Microsoft pointer attributes (like ``__ptr32`` and ``__ptr64``) and WebAssembly's ``__funcref``. - Fixed an issue where we tried to compare invalid NTTPs for variable declarations, which ended up in hitting an assertion with a constrained non-plain-auto NTTP, which we don't quite implement yet. (#GH208658) diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp index 0acfd14265904..1f8be1e3cc495 100644 --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -246,6 +246,14 @@ Sema::createLambdaClosureType(SourceRange IntroducerRange, TypeSourceInfo *Info, unsigned LambdaDependencyKind, LambdaCaptureDefault CaptureDefault) { DeclContext *DC = CurContext->getEnclosingNonExpansionStatementContext(); + // A deduction guide is never called or mangled, so a closure type in its + // signature, e.g. of the lambda in `-> A<T, decltype([](T) {})>`, must not + // be a local class of the guide: it becomes a template argument of the + // deduced class type, whose name is mangled. Create it in the context + // enclosing the guide instead, which is where the closure type of a lambda + // written in a deduction guide is created when the guide is parsed. + if (isa<CXXDeductionGuideDecl>(DC)) + DC = DC->getParent(); bool IsGenericLambda = Info && getGenericLambdaTemplateParameterList(getCurLambda(), *this); diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 21781f7474e56..8c3c6fc92f178 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -1778,6 +1778,16 @@ namespace { [](const auto &TA) { return TA.isDependent(); })) return CXXRecordDecl::LambdaDependencyKind::LDK_AlwaysDependent; } + // Substituting dependent template arguments yields another templated + // entity, so a lambda in it is dependent, like any lambda written in a + // template. TransformLambdaExpr cannot tell that from the DeclContext + // alone, e.g. when the template parameters of a deduction guide are + // rewritten in terms of those of the guide synthesized for an alias + // template, the lambda in the return type `A<T, decltype([](T) {})>` is + // substituted in an unevaluated, file-scope context and would otherwise + // become never-dependent although it still refers to T. + if (TemplateArgs.isAnyArgInstantiationDependent()) + return CXXRecordDecl::LambdaDependencyKind::LDK_AlwaysDependent; return inherited::ComputeLambdaDependency(LSI); } diff --git a/clang/test/AST/ByteCode/cxx2a.cpp b/clang/test/AST/ByteCode/cxx2a.cpp index e2cf74aa4ec57..0d067d99b5d66 100644 --- a/clang/test/AST/ByteCode/cxx2a.cpp +++ b/clang/test/AST/ByteCode/cxx2a.cpp @@ -276,7 +276,7 @@ namespace GH150705 { namespace DependentRequiresExpr { template <class T, - bool = []() -> bool { // both-error {{not a constant expression}} + bool = []() -> bool { if (requires { T::type; }) return true; return false; @@ -285,7 +285,13 @@ namespace DependentRequiresExpr { using type = void; }; - template <class T> using P = p<T>::type; // both-note {{while checking a default template argument}} + // The lambda is substituted with the dependent T of the alias, so it stays + // dependent, and the default template argument is not evaluated until the + // alias is used. + template <class T> using P = p<T>::type; + struct S { using type = int; }; + static_assert(__is_same(P<int>, void)); + static_assert(__is_same(P<S>, void)); } namespace PseudoDtorOnGlobal { diff --git a/clang/test/CodeGenCXX/cxx20-ctad-lambda-in-deduction-guide.cpp b/clang/test/CodeGenCXX/cxx20-ctad-lambda-in-deduction-guide.cpp new file mode 100644 index 0000000000000..5d41cc5b59c21 --- /dev/null +++ b/clang/test/CodeGenCXX/cxx20-ctad-lambda-in-deduction-guide.cpp @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s + +// The closure type of a lambda in the return type of a deduction guide becomes +// a template argument of the deduced class type. A deduction guide has no +// mangled name, so the closure type must be one of the scope enclosing the +// guide, and its call operator must have been instantiated along with the +// guide (`operator()(int)`, not `operator()(T)`). + +namespace alias_rhs { + +template <class T, class F> struct A { + A(T, F f = {}) { f({}); } +}; + +template <class T> using AA = A<T, decltype([](T) {})>; + +AA a{0}; +// CHECK-LABEL: define {{.*}} @_ZN9alias_rhs1AIiNS_UliE{{[0-9]*}}_EEC2EiS1_( +// CHECK: call void @_ZNK9alias_rhsUliE{{[0-9]*}}_clEi( +// CHECK-LABEL: define {{.*}} @_ZNK9alias_rhsUliE{{[0-9]*}}_clEi( +} // namespace alias_rhs + +namespace user_guide { + +template <class T, class F> struct A { + A(T, F f = {}) { f({}); } +}; + +template <class T> A(T) -> A<T, decltype([](T) {})>; + +A a{0}; +// CHECK-LABEL: define {{.*}} @_ZN10user_guide1AIiNS_UliE{{[0-9]*}}_EEC2EiS1_( +// CHECK: call void @_ZNK10user_guideUliE{{[0-9]*}}_clEi( +// CHECK-LABEL: define {{.*}} @_ZNK10user_guideUliE{{[0-9]*}}_clEi( +} // namespace user_guide diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp index f9d190f97abec..ccc0185790eb9 100644 --- a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp +++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp @@ -778,3 +778,32 @@ int arr3[3]; Arr3 a3(arr3, 0); static_assert(__is_same(decltype(a3), Arr<int, 3>)); } // namespace nttp_deduced_from_alias_in_nondeduced_param_type + +namespace lambda_in_alias_rhs { +// The lambda in the RHS of the alias is rewritten in terms of the template +// parameters of the synthesized deduction guide, and must remain dependent +// there, so that it is instantiated along with the guide. Otherwise, the call +// operator of the closure type in the deduced type would keep the parameter +// type T. +template <class T, class F> struct A { + constexpr A(T t, F f = {}) : v(f(t)) {} + int v; +}; + +template <class T> using AA = A<T, decltype([](T x) { return x + 1; })>; +constexpr AA a{41}; +static_assert(a.v == 42); + +// The lambda comes from the RHS of the alias that AAA is equivalent to. +template <class T> using AAA = AA<T>; +constexpr AAA aa{1}; +static_assert(aa.v == 2); + +template <class T, class F> struct B { + B(T, F f = {}) { f({}); } +}; + +template <class T> using BB = B<T, decltype([](T) {})>; +BB b{0}; + +} // namespace lambda_in_alias_rhs >From f1ec263c3e8dcd98a5a7424d2c0356532fa3b79e Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Fri, 4 Sep 2026 07:13:08 +0300 Subject: [PATCH 12/12] Make Younan Zhang happy and enhance deduction guide for type aliases to support template template parameters and reorder synthesized parameters accordingly --- clang/docs/ReleaseNotes.md | 10 +- clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 313 +++++++++--------- clang/test/SemaCXX/cxx20-ctad-type-alias.cpp | 24 ++ 3 files changed, 181 insertions(+), 166 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index febbe7353c2f0..8e080f370f8f4 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -479,10 +479,12 @@ features cannot lower the translation-unit ABI level; - Class template argument deduction through an alias template now works when a template parameter of the alias only appears in the synthesized deduction - guide through default template arguments. Such a template parameter now gets a default - template argument deduced from the return type of the underlying deduction - guide (here `Key` becomes the iterator's value type), instead of being - undeducible. + guide through default template arguments, such as `Key` in + `template <class Key, class Hash = std::hash<Key>> using MySet = std::unordered_set<Key, Hash>;` + when deducing `MySet s(first, last);`. Such a template parameter now gets a + default template argument deduced from the return type of the underlying + deduction guide (here `Key` becomes the iterator's value type), instead of + being undeducible. - Fixed a crash in class template argument deduction through an alias template when the alias fixes a non-type template parameter of the underlying template diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp index d85c84562a082..3ddf61b81a066 100644 --- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp +++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp @@ -888,6 +888,19 @@ struct ConvertConstructorToDeductionGuideTransform { } }; +// Returns the default template argument of the given template parameter, or +// null if it doesn't have one. +static const TemplateArgumentLoc *getDefaultArgument(const NamedDecl *Param) { + auto Get = [](const auto *P) -> const TemplateArgumentLoc * { + return P->hasDefaultArgument() ? &P->getDefaultArgument() : nullptr; + }; + if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) + return Get(TTP); + if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(Param)) + return Get(NTTP); + return Get(cast<TemplateTemplateParmDecl>(Param)); +} + // Find all template parameters that appear in the given DeducedArgs. // Return the indices of the template parameters in the TemplateParams. SmallVector<unsigned> TemplateParamsReferencedInTemplateArgumentList( @@ -899,24 +912,14 @@ SmallVector<unsigned> TemplateParamsReferencedInTemplateArgumentList( TemplateParamsList->getDepth(), ReferencedTemplateParams); - auto MarkDefaultArgs = [&](auto *Param) { - if (!Param->hasDefaultArgument()) - return; - SemaRef.MarkUsedTemplateParameters( - Param->getDefaultArgument().getArgument(), /*OnlyDeduced=*/false, - TemplateParamsList->getDepth(), ReferencedTemplateParams); - }; - for (unsigned Index = 0; Index < TemplateParamsList->size(); ++Index) { if (!ReferencedTemplateParams[Index]) continue; - auto *Param = TemplateParamsList->getParam(Index); - if (auto *TTPD = dyn_cast<TemplateTypeParmDecl>(Param)) - MarkDefaultArgs(TTPD); - else if (auto *NTTPD = dyn_cast<NonTypeTemplateParmDecl>(Param)) - MarkDefaultArgs(NTTPD); - else - MarkDefaultArgs(cast<TemplateTemplateParmDecl>(Param)); + if (const TemplateArgumentLoc *Default = + getDefaultArgument(TemplateParamsList->getParam(Index))) + SemaRef.MarkUsedTemplateParameters( + Default->getArgument(), /*OnlyDeduced=*/false, + TemplateParamsList->getDepth(), ReferencedTemplateParams); } SmallVector<unsigned> Results; @@ -975,19 +978,8 @@ struct FPrimeTemplateParamRef { // The index of the template parameter in the template parameter list of A or // f, respectively. unsigned Index; - // For a non-deduced template parameter of f: the deduced template parameters - // of f that it refers to. - llvm::SmallBitVector DeducedFParamsUsed; }; -static bool hasDefaultArgument(const NamedDecl *Param) { - if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) - return TTP->hasDefaultArgument(); - if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(Param)) - return NTTP->hasDefaultArgument(); - return cast<TemplateTemplateParmDecl>(Param)->hasDefaultArgument(); -} - static void setDefaultArgument(ASTContext &Context, NamedDecl *Param, const TemplateArgumentLoc &DefArg) { if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) @@ -1337,7 +1329,7 @@ static SmallVector<TemplateArgument> synthesizeDefaultArgumentsForFPrime( auto NeedsDefaultArgument = [&](unsigned Index) { NamedDecl *Param = AliasParams->getParam(Index); - return !Param->isTemplateParameterPack() && !hasDefaultArgument(Param); + return !Param->isTemplateParameterPack() && !getDefaultArgument(Param); }; if (llvm::none_of(AliasParamsInFPrime, NeedsDefaultArgument)) @@ -1394,81 +1386,75 @@ static SmallVector<TemplateArgument> synthesizeDefaultArgumentsForFPrime( return Result; } -// Determine the order of the template parameters of the deduction guide f' of -// the alias template A when some of them got a synthesized default template -// argument (see synthesizeDefaultArgumentsForFPrime). +// Mark the template parameters at the given depth that the given template +// parameter refers to: through its default template argument, its type (for a +// non-type template parameter), its type-constraint (for a type template +// parameter) or its template parameter list (for a template template +// parameter). +static void markReferencedTemplateParams(Sema &SemaRef, const NamedDecl *Param, + unsigned Depth, + llvm::SmallBitVector &Used) { + if (const TemplateArgumentLoc *Default = getDefaultArgument(Param)) + SemaRef.MarkUsedTemplateParameters(Default->getArgument(), + /*OnlyDeduced=*/false, Depth, Used); + if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) { + if (const TypeConstraint *TC = TTP->getTypeConstraint()) + if (const Expr *E = TC->getImmediatelyDeclaredConstraint()) + SemaRef.MarkUsedTemplateParameters(E, /*OnlyDeduced=*/false, Depth, + Used); + } else if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(Param)) { + SemaRef.MarkUsedTemplateParameters(TemplateArgument(NTTP->getType()), + /*OnlyDeduced=*/false, Depth, Used); + } else { + TemplateParameterList *TPL = + cast<TemplateTemplateParmDecl>(Param)->getTemplateParameters(); + for (const NamedDecl *P : *TPL) + markReferencedTemplateParams(SemaRef, P, Depth, Used); + if (const Expr *RC = TPL->getRequiresClause()) + SemaRef.MarkUsedTemplateParameters(RC, /*OnlyDeduced=*/false, Depth, + Used); + } +} + +// Reorder the template parameters of the deduction guide f' of the alias +// template A, given in the standard's order (the template parameters of A, +// followed by the non-deduced template parameters of f), when some of them got +// a synthesized default template argument (see +// synthesizeDefaultArgumentsForFPrime). // // A default template argument can only refer to preceding template parameters. -// The order given by the standard (the template parameters of A, followed by -// the non-deduced template parameters of f) doesn't satisfy that for the -// synthesized default template arguments, which refer to non-deduced template -// parameters of f. Instead, order the template parameters of f' such that each -// of them follows the ones its default template argument, its type (for a -// non-type template parameter) and its type-constraint refer to, staying as -// close to the standard's order as possible. +// The standard's order doesn't satisfy that for the synthesized default +// template arguments, which refer to non-deduced template parameters of f. +// Instead, order the template parameters of f' such that each of them follows +// the ones its default template argument, its type (for a non-type template +// parameter) and its type-constraint refer to, staying as close to the +// standard's order as possible. +// +// AliasParamsUsedByDeducedArg[I] holds the template parameters of A that the +// deduced template argument for the I-th template parameter of f refers to. // -// Returns false if there is no such order. -static bool orderFPrimeTemplateParameters( +// Returns false, leaving Params unchanged, if there is no such order. +static bool reorderFPrimeTemplateParameters( Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate, - FunctionTemplateDecl *F, ArrayRef<DeducedTemplateArgument> DeduceResults, - ArrayRef<unsigned> AliasParamsInFPrime, - ArrayRef<unsigned> NonDeducedFParams, + FunctionTemplateDecl *F, + ArrayRef<llvm::SmallBitVector> AliasParamsUsedByDeducedArg, ArrayRef<TemplateArgument> SynthesizedDefaultArgs, - SmallVectorImpl<FPrimeTemplateParamRef> &Order) { + SmallVectorImpl<FPrimeTemplateParamRef> &Params) { TemplateParameterList *AliasParams = AliasTemplate->getTemplateParameters(); TemplateParameterList *FParams = F->getTemplateParameters(); + unsigned NumParams = Params.size(); - // The template parameters of f' in the standard's order, and the position - // of each template parameter of A / non-deduced template parameter of f in - // that list. - SmallVector<FPrimeTemplateParamRef> Params; + // The position in Params of each template parameter of A / non-deduced + // template parameter of f. SmallVector<unsigned> AliasParamPos(AliasParams->size(), InvalidFPrimeIndex); SmallVector<unsigned> FParamPos(FParams->size(), InvalidFPrimeIndex); - - for (unsigned Index : AliasParamsInFPrime) { - AliasParamPos[Index] = Params.size(); - Params.push_back( - {/*IsAliasParam=*/true, Index, llvm::SmallBitVector(FParams->size())}); - } - - for (unsigned Index : NonDeducedFParams) { - FParamPos[Index] = Params.size(); - Params.push_back( - {/*IsAliasParam=*/false, Index, llvm::SmallBitVector(FParams->size())}); + for (auto [Pos, P] : llvm::enumerate(Params)) { + if (P.IsAliasParam) + AliasParamPos[P.Index] = Pos; + else + FParamPos[P.Index] = Pos; } - unsigned NumParams = Params.size(); - - // Mark the template parameters (of the list containing Param, at the given - // depth) that the default template argument, the type and the - // type-constraint of Param refer to. - auto MarkReferencedTemplateParams = [&](NamedDecl *Param, unsigned Depth, - llvm::SmallBitVector &Used) { - if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(Param)) { - if (TTP->hasDefaultArgument()) - SemaRef.MarkUsedTemplateParameters( - TTP->getDefaultArgument().getArgument(), /*OnlyDeduced=*/false, - Depth, Used); - if (const TypeConstraint *TC = TTP->getTypeConstraint()) - if (const Expr *E = TC->getImmediatelyDeclaredConstraint()) - SemaRef.MarkUsedTemplateParameters(E, /*OnlyDeduced=*/false, Depth, - Used); - } else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(Param)) { - if (NTTP->hasDefaultArgument()) - SemaRef.MarkUsedTemplateParameters( - NTTP->getDefaultArgument().getArgument(), /*OnlyDeduced=*/false, - Depth, Used); - SemaRef.MarkUsedTemplateParameters(TemplateArgument(NTTP->getType()), - /*OnlyDeduced=*/false, Depth, Used); - } else { - auto *TTP = cast<TemplateTemplateParmDecl>(Param); - if (TTP->hasDefaultArgument()) - SemaRef.MarkUsedTemplateParameters( - TTP->getDefaultArgument().getArgument(), /*OnlyDeduced=*/false, - Depth, Used); - } - }; - // Deps[I] holds the positions of the template parameters that must precede // the I-th template parameter. SmallVector<llvm::SmallBitVector> Deps(NumParams, @@ -1476,7 +1462,7 @@ static bool orderFPrimeTemplateParameters( for (auto [Pos, P] : llvm::enumerate(Params)) { if (P.IsAliasParam) { llvm::SmallBitVector UsedAliasParams(AliasParams->size()); - MarkReferencedTemplateParams(AliasParams->getParam(P.Index), + markReferencedTemplateParams(SemaRef, AliasParams->getParam(P.Index), AliasParams->getDepth(), UsedAliasParams); for (unsigned Index : UsedAliasParams.set_bits()) if (Index != P.Index && AliasParamPos[Index] != InvalidFPrimeIndex) @@ -1498,7 +1484,7 @@ static bool orderFPrimeTemplateParameters( } llvm::SmallBitVector UsedFParams(FParams->size()); - MarkReferencedTemplateParams(FParams->getParam(P.Index), + markReferencedTemplateParams(SemaRef, FParams->getParam(P.Index), FParams->getDepth(), UsedFParams); for (unsigned Index : UsedFParams.set_bits()) { if (Index == P.Index) @@ -1509,13 +1495,7 @@ static bool orderFPrimeTemplateParameters( } // A deduced template parameter of f, which is replaced in f' by the // template parameters of A that its deduced argument refers to. - P.DeducedFParamsUsed.set(Index); - const TemplateArgument &DeducedArg = DeduceResults[Index]; - llvm::SmallBitVector UsedAliasParams(AliasParams->size()); - SemaRef.MarkUsedTemplateParameters(DeducedArg, /*OnlyDeduced=*/false, - AliasParams->getDepth(), - UsedAliasParams); - for (unsigned AliasIndex : UsedAliasParams.set_bits()) + for (unsigned AliasIndex : AliasParamsUsedByDeducedArg[Index].set_bits()) if (AliasParamPos[AliasIndex] != InvalidFPrimeIndex) Deps[Pos].set(AliasParamPos[AliasIndex]); } @@ -1523,6 +1503,7 @@ static bool orderFPrimeTemplateParameters( // Repeatedly pick the first template parameter all of whose dependencies // have been placed. + SmallVector<FPrimeTemplateParamRef> Order; llvm::SmallBitVector Placed(NumParams); while (Order.size() < NumParams) { unsigned Next = NumParams; @@ -1532,8 +1513,9 @@ static bool orderFPrimeTemplateParameters( if (Next == NumParams) // The dependencies are circular. return false; Placed.set(Next); - Order.push_back(std::move(Params[Next])); + Order.push_back(Params[Next]); } + Params = std::move(Order); return true; } @@ -1545,6 +1527,8 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( FunctionTemplateDecl *F = SourceDeductionGuide->getDescribedFunctionTemplate(); assert(F && "deduction guide for alias template must be a function template"); + TemplateParameterList *AliasParams = AliasTemplate->getTemplateParameters(); + TemplateParameterList *FParams = F->getTemplateParameters(); LocalInstantiationScope Scope(SemaRef); Sema::NonSFINAEContext _1(SemaRef); @@ -1618,36 +1602,42 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( // the return type of the deduction guide from it: Y->int, X->U sema::TemplateDeductionInfo TDeduceInfo(Loc); // Must initialize n elements, this is required by DeduceTemplateArguments. - SmallVector<DeducedTemplateArgument> DeduceResults( - F->getTemplateParameters()->size()); + SmallVector<DeducedTemplateArgument> DeduceResults(FParams->size()); // FIXME: DeduceTemplateArguments stops immediately at the first // non-deducible template argument. However, this doesn't seem to cause // issues for practice cases, we probably need to extend it to continue // performing deduction for rest of arguments to align with the C++ // standard. - SemaRef.DeduceTemplateArguments( - F->getTemplateParameters(), FReturnTemplateArgs, - AliasRhsTemplateArgs, TDeduceInfo, DeduceResults, - /*NumberOfArgumentsMustMatch=*/false); + SemaRef.DeduceTemplateArguments(FParams, FReturnTemplateArgs, + AliasRhsTemplateArgs, TDeduceInfo, + DeduceResults, + /*NumberOfArgumentsMustMatch=*/false); SmallVector<TemplateArgument> DeducedArgs; SmallVector<unsigned> NonDeducedTemplateParamsInFIndex; + // The template parameters of A that the deduced template argument for each + // template parameter of f refers to (none for the non-deduced ones). + SmallVector<llvm::SmallBitVector> AliasParamsUsedByDeducedArg( + FParams->size(), llvm::SmallBitVector(AliasParams->size())); // !!NOTE: DeduceResults respects the sequence of template parameters of // the deduction guide f. for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) { - const auto &D = DeduceResults[Index]; - if (!IsNonDeducedArgument(D)) - DeducedArgs.push_back(D); - else + const TemplateArgument &D = DeduceResults[Index]; + if (IsNonDeducedArgument(D)) { NonDeducedTemplateParamsInFIndex.push_back(Index); + continue; + } + DeducedArgs.push_back(D); + SemaRef.MarkUsedTemplateParameters(D, /*OnlyDeduced=*/false, + AliasParams->getDepth(), + AliasParamsUsedByDeducedArg[Index]); } auto DeducedAliasTemplateParams = - TemplateParamsReferencedInTemplateArgumentList( - SemaRef, AliasTemplate->getTemplateParameters(), DeducedArgs); + TemplateParamsReferencedInTemplateArgumentList(SemaRef, AliasParams, + DeducedArgs); // All template arguments null by default. - SmallVector<TemplateArgument> TemplateArgsForBuildingFPrime( - F->getTemplateParameters()->size()); + SmallVector<TemplateArgument> TemplateArgsForBuildingFPrime(FParams->size()); // The same template arguments, but with the deduced non-type template // arguments as (not yet converted) expressions, for rewriting the template // parameters of f in terms of those of f'. The rewrite requires expressions, @@ -1656,11 +1646,7 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( // rewritten as the expression `false` in the type of a template parameter // `std::enable_if_t<!B, int> = 0` of f, but instantiated as the integral // value. - SmallVector<TemplateArgument> TemplateArgsForRewritingFPrime( - F->getTemplateParameters()->size()); - - TemplateParameterList *AliasParams = AliasTemplate->getTemplateParameters(); - TemplateParameterList *FParams = F->getTemplateParameters(); + SmallVector<TemplateArgument> TemplateArgsForRewritingFPrime(FParams->size()); // Create a template parameter list for the synthesized deduction guide f'. // @@ -1682,6 +1668,15 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( InvalidFPrimeIndex); SmallVector<unsigned> FParamFPrimeIndex(FParams->size(), InvalidFPrimeIndex); + // The template parameters of f', in the standard's order: the template + // parameters of A that appear in the deductions, followed by the non-deduced + // template parameters of f. + SmallVector<FPrimeTemplateParamRef> FPrimeParamOrder; + for (unsigned Index : DeducedAliasTemplateParams) + FPrimeParamOrder.push_back({/*IsAliasParam=*/true, Index}); + for (unsigned Index : NonDeducedTemplateParamsInFIndex) + FPrimeParamOrder.push_back({/*IsAliasParam=*/false, Index}); + // Template parameters of A that appear in f' without a default template // argument, and that cannot be deduced from the function parameters of f', // get a default template argument synthesized from the return type of f. @@ -1693,16 +1688,12 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( // after the template parameters of A; reorder the template parameters of f' // so that default template arguments only refer to preceding template // parameters. If that is not possible, don't synthesize any. - SmallVector<FPrimeTemplateParamRef> FPrimeParamOrder; if (llvm::any_of(SynthesizedDefaultArgs, [](const TemplateArgument &TA) { return !TA.isNull(); }) && - !orderFPrimeTemplateParameters( - SemaRef, AliasTemplate, F, DeduceResults, DeducedAliasTemplateParams, - NonDeducedTemplateParamsInFIndex, SynthesizedDefaultArgs, - FPrimeParamOrder)) { + !reorderFPrimeTemplateParameters( + SemaRef, AliasTemplate, F, AliasParamsUsedByDeducedArg, + SynthesizedDefaultArgs, FPrimeParamOrder)) llvm::fill(SynthesizedDefaultArgs, TemplateArgument()); - FPrimeParamOrder.clear(); - } // We might be already within a pack expansion, but rewriting template // parameters is independent of that. (We may or may not expand new packs @@ -1840,42 +1831,42 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( return true; }; - if (FPrimeParamOrder.empty()) { - // The standard's order: the template parameters of A that appear in the - // deductions, followed by the non-deduced template parameters of f. - for (unsigned AliasTemplateParamIdx : DeducedAliasTemplateParams) - if (!AddAliasTemplateParam(AliasTemplateParamIdx)) - return nullptr; - for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) - if (!IsNonDeducedArgument(DeduceResults[Index]) && - !SubstDeducedTemplateArg(Index)) - return nullptr; - for (unsigned FTemplateParamIdx : NonDeducedTemplateParamsInFIndex) - if (!AddFTemplateParam(FTemplateParamIdx)) - return nullptr; - } else { - for (const FPrimeTemplateParamRef &P : FPrimeParamOrder) { - if (P.IsAliasParam) { - if (!AddAliasTemplateParam(P.Index)) - return nullptr; + // Substitute the deduced template arguments (1) that haven't been substituted + // yet and whose template parameters of A have all been added to f' already. + auto SubstDeducedTemplateArgs = [&]() -> bool { + for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) { + if (IsNonDeducedArgument(DeduceResults[Index]) || + !TemplateArgsForBuildingFPrime[Index].isNull()) continue; - } - // The template parameters of A that the deduced template arguments - // referred to by this template parameter of f refer to have been added - // already, so we can substitute them now. - for (unsigned Index : P.DeducedFParamsUsed.set_bits()) - if (TemplateArgsForBuildingFPrime[Index].isNull() && - !SubstDeducedTemplateArg(Index)) - return nullptr; - if (!AddFTemplateParam(P.Index)) - return nullptr; + if (llvm::any_of(AliasParamsUsedByDeducedArg[Index].set_bits(), + [&](unsigned AliasIndex) { + return AliasParamFPrimeIndex[AliasIndex] == + InvalidFPrimeIndex; + })) + continue; + if (!SubstDeducedTemplateArg(Index)) + return false; } - for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) - if (!IsNonDeducedArgument(DeduceResults[Index]) && - TemplateArgsForBuildingFPrime[Index].isNull() && - !SubstDeducedTemplateArg(Index)) + return true; + }; + + for (const FPrimeTemplateParamRef &P : FPrimeParamOrder) { + if (P.IsAliasParam) { + if (!AddAliasTemplateParam(P.Index)) return nullptr; + continue; + } + // A non-deduced template parameter of f may refer to deduced template + // parameters of f, whose deduced template arguments must have been + // substituted by then. In the standard's order, all of them can be, as all + // the template parameters of A precede the template parameters of f. + if (!SubstDeducedTemplateArgs() || !AddFTemplateParam(P.Index)) + return nullptr; } + // Substitute the deduced template arguments that no template parameter of f' + // needed. + if (!SubstDeducedTemplateArgs()) + return nullptr; auto *TemplateArgListForBuildingFPrime = TemplateArgumentList::CreateCopy(Context, TemplateArgsForBuildingFPrime); @@ -1894,10 +1885,8 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias( TemplateParameterList *FPrimeTemplateParamList = nullptr; if (!FPrimeTemplateParams.empty()) FPrimeTemplateParamList = TemplateParameterList::Create( - Context, AliasTemplate->getTemplateParameters()->getTemplateLoc(), - AliasTemplate->getTemplateParameters()->getLAngleLoc(), - FPrimeTemplateParams, - AliasTemplate->getTemplateParameters()->getRAngleLoc(), + Context, AliasParams->getTemplateLoc(), AliasParams->getLAngleLoc(), + FPrimeTemplateParams, AliasParams->getRAngleLoc(), /*RequiresClause=*/RequiresClause); auto *DGuide = buildDeductionGuide( diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp index ccc0185790eb9..867ee36ee7f63 100644 --- a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp +++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp @@ -730,6 +730,30 @@ template <class U> void h2(Iter b, Iter e) { static_assert(__is_same(decltype(s), CSet<int, hash<int>>)); } template void h2<char>(Iter, Iter); + +// A template template parameter of the alias refers to another template +// parameter of the alias in its own template parameter list, so it must follow +// that one when the template parameters of the synthesized guide are reordered. +template <auto> struct Def {}; +template <class Key, class Cmp = hash<Key>, template <Cmp> class TT = Def> +struct TSet { + template <class It> TSet(It, It, Cmp); +}; +template <class It, class Cmp, template <Cmp> class TT = Def> +TSet(It, It, Cmp) -> TSet<typename iter_traits<It>::value_type, Cmp, TT>; +template <class Key, class Cmp = hash<Key>, template <Cmp> class TT = Def> +using MyTSet = TSet<Key, Cmp, TT>; // #MyTSet + +void t(Iter b, Iter e) { + MyTSet s1(b, e, hash<int>()); + static_assert(__is_same(decltype(s1), TSet<int, hash<int>, Def>)); + + MyTSet s2(b, e, 1, 2); // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'MyTSet'}} + // expected-note@#MyTSet {{implicit deduction guide declared as 'template <class It, class Key = typename iter_traits<It>::value_type, class Cmp = hash<Key>, template <Cmp> class TT = Def> requires __is_deducible(synthesized_default_args::MyTSet, TSet<typename iter_traits<It>::value_type, Cmp, TT>) MyTSet(It, It, Cmp) -> TSet<typename iter_traits<It>::value_type, Cmp, TT>'}} + // expected-note@#MyTSet 2 {{implicit deduction guide declared as}} + // expected-note@#MyTSet 2 {{requires 3 arguments, but 4 were provided}} + // expected-note@#MyTSet {{requires 1 argument, but 4 were provided}} +} } // namespace synthesized_default_args namespace nttp_deduced_from_alias_in_nondeduced_param_type { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
