[clang] [Clang][C++17/20] Add full support to deduction guides (PR #193056)
Muhammad Bassiouni via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 2 21:02:52 PDT 2026
https://github.com/bassiounix updated https://github.com/llvm/llvm-project/pull/193056
>From 39b87cfe469336770e1d94e6bea5f50f6fae75d4 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Mon, 20 Apr 2026 20:50:53 +0200
Subject: [PATCH 1/7] [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 <muhammad.m.bassiouni at gmail.com>
Date: Mon, 20 Apr 2026 22:35:16 +0200
Subject: [PATCH 2/7] 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 <muhammad.m.bassiouni at gmail.com>
Date: Tue, 21 Apr 2026 15:42:00 +0200
Subject: [PATCH 3/7] 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 <muhammad.m.bassiouni at gmail.com>
Date: Tue, 21 Apr 2026 15:42:17 +0200
Subject: [PATCH 4/7] 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 <muhammad.m.bassiouni at gmail.com>
Date: Wed, 2 Sep 2026 22:22:34 +0300
Subject: [PATCH 5/7] 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 <muhammad.m.bassiouni at gmail.com>
Date: Wed, 2 Sep 2026 22:25:02 +0300
Subject: [PATCH 6/7] 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 <muhammad.m.bassiouni at gmail.com>
Date: Thu, 3 Sep 2026 06:03:55 +0300
Subject: [PATCH 7/7] 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
More information about the cfe-commits
mailing list