Skip to content

Commit bee78b8

Browse files
authored
Reland "[clang][Sema] Use original template pattern when declaring implicit deduction guides for nested template classes" (#73087)
Reland of f418319 with proper handling of template constructors When a nested template is instantiated, the template pattern of the inner class is not copied into the outer class ClassTemplateSpecializationDecl. The specialization contains a ClassTemplateDecl with an empty record that points to the original template pattern instead. As a result, when looking up the constructors of the inner class, no results are returned. This patch finds the original template pattern and uses that for the lookup instead. Based on CWG2471 we must also substitute the known outer template arguments when creating deduction guides for the inner class. Changes from last iteration: 1. In template constructors, arguments are first rewritten to depth - 1 relative to the constructor as compared to depth 0 originally. These arguments are needed for substitution into constraint expressions. 2. Outer arguments are then applied with the template instantiator to produce a template argument at depth zero for use in the deduction guide. This substitution does not evaluate constraints, which preserves constraint arguments at the correct depth for later evaluation. 3. Tests are added that cover template constructors within nested deduction guides for all special substitution cases. 4. Computation of the template pattern and outer instantiation arguments are pulled into the constructor of `ConvertConstructorToDeductionGuideTransform`.
1 parent 75a9ed4 commit bee78b8

File tree

4 files changed

+130
-6
lines changed

4 files changed

+130
-6
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,11 @@ Bug Fixes to C++ Support
741741
declaration definition. Fixes:
742742
(`#61763 <https://github.com/llvm/llvm-project/issues/61763>`_)
743743

744+
- Fix a bug where implicit deduction guides are not correctly generated for nested template
745+
classes. Fixes:
746+
(`#46200 <https://github.com/llvm/llvm-project/issues/46200>`_)
747+
(`#57812 <https://github.com/llvm/llvm-project/issues/57812>`_)
748+
744749
- Diagnose use of a variable-length array in a coroutine. The design of
745750
coroutines is such that it is not possible to support VLA use. Fixes:
746751
(`#65858 <https://github.com/llvm/llvm-project/issues/65858>`_)

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2250,10 +2250,24 @@ class ExtractTypeForDeductionGuide
22502250
struct ConvertConstructorToDeductionGuideTransform {
22512251
ConvertConstructorToDeductionGuideTransform(Sema &S,
22522252
ClassTemplateDecl *Template)
2253-
: SemaRef(S), Template(Template) {}
2253+
: SemaRef(S), Template(Template) {
2254+
// If the template is nested, then we need to use the original
2255+
// pattern to iterate over the constructors.
2256+
ClassTemplateDecl *Pattern = Template;
2257+
while (Pattern->getInstantiatedFromMemberTemplate()) {
2258+
if (Pattern->isMemberSpecialization())
2259+
break;
2260+
Pattern = Pattern->getInstantiatedFromMemberTemplate();
2261+
NestedPattern = Pattern;
2262+
}
2263+
2264+
if (NestedPattern)
2265+
OuterInstantiationArgs = SemaRef.getTemplateInstantiationArgs(Template);
2266+
}
22542267

22552268
Sema &SemaRef;
22562269
ClassTemplateDecl *Template;
2270+
ClassTemplateDecl *NestedPattern = nullptr;
22572271

22582272
DeclContext *DC = Template->getDeclContext();
22592273
CXXRecordDecl *Primary = Template->getTemplatedDecl();
@@ -2266,6 +2280,10 @@ struct ConvertConstructorToDeductionGuideTransform {
22662280
// depth-0 template parameters.
22672281
unsigned Depth1IndexAdjustment = Template->getTemplateParameters()->size();
22682282

2283+
// Instantiation arguments for the outermost depth-1 templates
2284+
// when the template is nested
2285+
MultiLevelTemplateArgumentList OuterInstantiationArgs;
2286+
22692287
/// Transform a constructor declaration into a deduction guide.
22702288
NamedDecl *transformConstructor(FunctionTemplateDecl *FTD,
22712289
CXXConstructorDecl *CD) {
@@ -2284,21 +2302,43 @@ struct ConvertConstructorToDeductionGuideTransform {
22842302
if (FTD) {
22852303
TemplateParameterList *InnerParams = FTD->getTemplateParameters();
22862304
SmallVector<NamedDecl *, 16> AllParams;
2305+
SmallVector<TemplateArgument, 16> Depth1Args;
22872306
AllParams.reserve(TemplateParams->size() + InnerParams->size());
22882307
AllParams.insert(AllParams.begin(),
22892308
TemplateParams->begin(), TemplateParams->end());
22902309
SubstArgs.reserve(InnerParams->size());
2310+
Depth1Args.reserve(InnerParams->size());
22912311

22922312
// Later template parameters could refer to earlier ones, so build up
22932313
// a list of substituted template arguments as we go.
22942314
for (NamedDecl *Param : *InnerParams) {
22952315
MultiLevelTemplateArgumentList Args;
22962316
Args.setKind(TemplateSubstitutionKind::Rewrite);
2297-
Args.addOuterTemplateArguments(SubstArgs);
2317+
Args.addOuterTemplateArguments(Depth1Args);
22982318
Args.addOuterRetainedLevel();
2319+
if (NestedPattern)
2320+
Args.addOuterRetainedLevels(NestedPattern->getTemplateDepth());
22992321
NamedDecl *NewParam = transformTemplateParameter(Param, Args);
23002322
if (!NewParam)
23012323
return nullptr;
2324+
2325+
// Constraints require that we substitute depth-1 arguments
2326+
// to match depths when substituted for evaluation later
2327+
Depth1Args.push_back(SemaRef.Context.getCanonicalTemplateArgument(
2328+
SemaRef.Context.getInjectedTemplateArg(NewParam)));
2329+
2330+
if (NestedPattern) {
2331+
TemplateDeclInstantiator Instantiator(SemaRef, DC,
2332+
OuterInstantiationArgs);
2333+
Instantiator.setEvaluateConstraints(false);
2334+
SemaRef.runWithSufficientStackSpace(NewParam->getLocation(), [&] {
2335+
NewParam = cast<NamedDecl>(Instantiator.Visit(NewParam));
2336+
});
2337+
}
2338+
2339+
assert(NewParam->getTemplateDepth() == 0 &&
2340+
"Unexpected template parameter depth");
2341+
23022342
AllParams.push_back(NewParam);
23032343
SubstArgs.push_back(SemaRef.Context.getCanonicalTemplateArgument(
23042344
SemaRef.Context.getInjectedTemplateArg(NewParam)));
@@ -2309,8 +2349,10 @@ struct ConvertConstructorToDeductionGuideTransform {
23092349
if (Expr *InnerRC = InnerParams->getRequiresClause()) {
23102350
MultiLevelTemplateArgumentList Args;
23112351
Args.setKind(TemplateSubstitutionKind::Rewrite);
2312-
Args.addOuterTemplateArguments(SubstArgs);
2352+
Args.addOuterTemplateArguments(Depth1Args);
23132353
Args.addOuterRetainedLevel();
2354+
if (NestedPattern)
2355+
Args.addOuterRetainedLevels(NestedPattern->getTemplateDepth());
23142356
ExprResult E = SemaRef.SubstExpr(InnerRC, Args);
23152357
if (E.isInvalid())
23162358
return nullptr;
@@ -2333,6 +2375,9 @@ struct ConvertConstructorToDeductionGuideTransform {
23332375
Args.addOuterRetainedLevel();
23342376
}
23352377

2378+
if (NestedPattern)
2379+
Args.addOuterRetainedLevels(NestedPattern->getTemplateDepth());
2380+
23362381
FunctionProtoTypeLoc FPTL = CD->getTypeSourceInfo()->getTypeLoc()
23372382
.getAsAdjusted<FunctionProtoTypeLoc>();
23382383
assert(FPTL && "no prototype for constructor declaration");
@@ -2394,7 +2439,7 @@ struct ConvertConstructorToDeductionGuideTransform {
23942439
// substitute it directly.
23952440
auto *NewTTP = TemplateTypeParmDecl::Create(
23962441
SemaRef.Context, DC, TTP->getBeginLoc(), TTP->getLocation(),
2397-
/*Depth*/ 0, Depth1IndexAdjustment + TTP->getIndex(),
2442+
TTP->getDepth() - 1, Depth1IndexAdjustment + TTP->getIndex(),
23982443
TTP->getIdentifier(), TTP->wasDeclaredWithTypename(),
23992444
TTP->isParameterPack(), TTP->hasTypeConstraint(),
24002445
TTP->isExpandedParameterPack()
@@ -2429,7 +2474,8 @@ struct ConvertConstructorToDeductionGuideTransform {
24292474
// the index of the parameter once it's done.
24302475
auto *NewParam =
24312476
cast<TemplateParmDecl>(SemaRef.SubstDecl(OldParam, DC, Args));
2432-
assert(NewParam->getDepth() == 0 && "unexpected template param depth");
2477+
assert(NewParam->getDepth() == OldParam->getDepth() - 1 &&
2478+
"unexpected template param depth");
24332479
NewParam->setPosition(NewParam->getPosition() + Depth1IndexAdjustment);
24342480
return NewParam;
24352481
}
@@ -2446,6 +2492,9 @@ struct ConvertConstructorToDeductionGuideTransform {
24462492
for (auto *OldParam : TL.getParams()) {
24472493
ParmVarDecl *NewParam =
24482494
transformFunctionTypeParam(OldParam, Args, MaterializedTypedefs);
2495+
if (NestedPattern && NewParam)
2496+
NewParam = transformFunctionTypeParam(NewParam, OuterInstantiationArgs,
2497+
MaterializedTypedefs);
24492498
if (!NewParam)
24502499
return QualType();
24512500
ParamTypes.push_back(NewParam->getType());
@@ -2655,9 +2704,12 @@ void Sema::DeclareImplicitDeductionGuides(TemplateDecl *Template,
26552704
// FIXME: Skip constructors for which deduction must necessarily fail (those
26562705
// for which some class template parameter without a default argument never
26572706
// appears in a deduced context).
2707+
ClassTemplateDecl *Pattern =
2708+
Transform.NestedPattern ? Transform.NestedPattern : Transform.Template;
2709+
ContextRAII SavedContext(*this, Pattern->getTemplatedDecl());
26582710
llvm::SmallPtrSet<NamedDecl *, 8> ProcessedCtors;
26592711
bool AddedAny = false;
2660-
for (NamedDecl *D : LookupConstructors(Transform.Primary)) {
2712+
for (NamedDecl *D : LookupConstructors(Pattern->getTemplatedDecl())) {
26612713
D = D->getUnderlyingDecl();
26622714
if (D->isInvalidDecl() || D->isImplicit())
26632715
continue;
@@ -2703,6 +2755,8 @@ void Sema::DeclareImplicitDeductionGuides(TemplateDecl *Template,
27032755
Transform.buildSimpleDeductionGuide(Transform.DeducedType))
27042756
->getTemplatedDecl())
27052757
->setDeductionCandidateKind(DeductionCandidate::Copy);
2758+
2759+
SavedContext.pop();
27062760
}
27072761

27082762
/// Diagnose the presence of a default template argument on a

clang/test/SemaTemplate/nested-deduction-guides.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44
template<typename T> struct A {
55
template<typename U> struct B {
66
B(...);
7+
B(const B &) = default;
78
};
89
template<typename U> B(U) -> B<U>;
910
};
1011
A<void>::B b = 123;
12+
A<void>::B copy = b;
1113

1214
using T = decltype(b);
1315
using T = A<void>::B<int>;
16+
17+
using Copy = decltype(copy);
18+
using Copy = A<void>::B<int>;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %clang_cc1 -std=c++20 -verify %s
2+
// expected-no-diagnostics
3+
4+
template<class T> struct S {
5+
template<class U> struct N {
6+
N(T) {}
7+
N(T, U) {}
8+
template<class V> N(V, U) {}
9+
};
10+
};
11+
12+
S<int>::N x{"a", 1};
13+
using T = decltype(x);
14+
using T = S<int>::N<int>;
15+
16+
template<class X> struct default_ftd_argument {
17+
template<class Y> struct B {
18+
template<class W = X, class Z = Y, class V = Z, int I = 0> B(Y);
19+
};
20+
};
21+
22+
default_ftd_argument<int>::B default_arg("a");
23+
using DefaultArg = decltype(default_arg);
24+
using DefaultArg = default_ftd_argument<int>::B<const char *>;
25+
26+
template<bool> struct test;
27+
template<class X> struct non_type_param {
28+
template<class Y> struct B {
29+
B(Y);
30+
template<class Z, test<Z::value> = 0> B(Z);
31+
};
32+
};
33+
34+
non_type_param<int>::B ntp = 5;
35+
using NonTypeParam = decltype(ntp);
36+
using NonTypeParam = non_type_param<int>::B<int>;
37+
38+
template<typename A, typename T>
39+
concept C = (sizeof(T) == sizeof(A));
40+
41+
template<class X> struct concepts {
42+
template<class Y> struct B {
43+
template<class K = X, C<K> Z> B(Y, Z);
44+
};
45+
};
46+
47+
concepts<int>::B cc(1, 3);
48+
using Concepts = decltype(cc);
49+
using Concepts = concepts<int>::B<int>;
50+
51+
template<class X> struct requires_clause {
52+
template<class Y> struct B {
53+
template<class Z> requires (sizeof(Z) == sizeof(X))
54+
B(Y, Z);
55+
};
56+
};
57+
58+
requires_clause<int>::B req(1, 2);
59+
using RC = decltype(req);
60+
using RC = requires_clause<int>::B<int>;

0 commit comments

Comments
 (0)