Skip to content

[clang] CTAD: build aggregate deduction guides for alias templates. #85904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -9713,7 +9713,8 @@ class Sema final {
/// not already done so.
void DeclareImplicitDeductionGuides(TemplateDecl *Template,
SourceLocation Loc);
FunctionTemplateDecl *DeclareImplicitDeductionGuideFromInitList(

FunctionTemplateDecl *DeclareAggregateDeductionGuideFromInitList(
TemplateDecl *Template, MutableArrayRef<QualType> ParamTypes,
SourceLocation Loc);

Expand Down
28 changes: 6 additions & 22 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10930,32 +10930,16 @@ QualType Sema::DeduceTemplateSpecializationFromInitializer(
Context.getLValueReferenceType(ElementTypes[I].withConst());
}

llvm::FoldingSetNodeID ID;
ID.AddPointer(Template);
for (auto &T : ElementTypes)
T.getCanonicalType().Profile(ID);
unsigned Hash = ID.ComputeHash();
if (AggregateDeductionCandidates.count(Hash) == 0) {
if (FunctionTemplateDecl *TD =
DeclareImplicitDeductionGuideFromInitList(
Template, ElementTypes,
TSInfo->getTypeLoc().getEndLoc())) {
auto *GD = cast<CXXDeductionGuideDecl>(TD->getTemplatedDecl());
GD->setDeductionCandidateKind(DeductionCandidate::Aggregate);
AggregateDeductionCandidates[Hash] = GD;
addDeductionCandidate(TD, GD, DeclAccessPair::make(TD, AS_public),
OnlyListConstructors,
/*AllowAggregateDeductionCandidate=*/true);
}
} else {
CXXDeductionGuideDecl *GD = AggregateDeductionCandidates[Hash];
FunctionTemplateDecl *TD = GD->getDescribedFunctionTemplate();
assert(TD && "aggregate deduction candidate is function template");
if (FunctionTemplateDecl *TD =
DeclareAggregateDeductionGuideFromInitList(
LookupTemplateDecl, ElementTypes,
TSInfo->getTypeLoc().getEndLoc())) {
auto *GD = cast<CXXDeductionGuideDecl>(TD->getTemplatedDecl());
addDeductionCandidate(TD, GD, DeclAccessPair::make(TD, AS_public),
OnlyListConstructors,
/*AllowAggregateDeductionCandidate=*/true);
HasAnyDeductionGuide = true;
}
HasAnyDeductionGuide = true;
}
};

Expand Down
209 changes: 162 additions & 47 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2754,23 +2754,42 @@ bool hasDeclaredDeductionGuides(DeclarationName Name, DeclContext *DC) {
return false;
}

// Build deduction guides for a type alias template.
void DeclareImplicitDeductionGuidesForTypeAlias(
Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate, SourceLocation Loc) {
if (AliasTemplate->isInvalidDecl())
return;
auto &Context = SemaRef.Context;
// FIXME: if there is an explicit deduction guide after the first use of the
// type alias usage, we will not cover this explicit deduction guide. fix this
// case.
if (hasDeclaredDeductionGuides(
Context.DeclarationNames.getCXXDeductionGuideName(AliasTemplate),
AliasTemplate->getDeclContext()))
return;
NamedDecl *transformTemplateParameter(Sema &SemaRef, DeclContext *DC,
NamedDecl *TemplateParam,
MultiLevelTemplateArgumentList &Args,
unsigned NewIndex) {
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
return transformTemplateTypeParam(SemaRef, DC, TTP, Args, TTP->getDepth(),
NewIndex);
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex,
TTP->getDepth());
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex,
NTTP->getDepth());
llvm_unreachable("Unhandled template parameter types");
}

Expr *transformRequireClause(Sema &SemaRef, FunctionTemplateDecl *FTD,
llvm::ArrayRef<TemplateArgument> TransformedArgs) {
Expr *RC = FTD->getTemplateParameters()->getRequiresClause();
if (!RC)
return nullptr;
MultiLevelTemplateArgumentList Args;
Args.setKind(TemplateSubstitutionKind::Rewrite);
Args.addOuterTemplateArguments(TransformedArgs);
ExprResult E = SemaRef.SubstExpr(RC, Args);
if (E.isInvalid())
return nullptr;
return E.getAs<Expr>();
}

std::pair<TemplateDecl *, llvm::ArrayRef<TemplateArgument>>
getRHSTemplateDeclAndArgs(Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate) {
// Unwrap the sugared ElaboratedType.
auto RhsType = AliasTemplate->getTemplatedDecl()
->getUnderlyingType()
.getSingleStepDesugaredType(Context);
.getSingleStepDesugaredType(SemaRef.Context);
TemplateDecl *Template = nullptr;
llvm::ArrayRef<TemplateArgument> AliasRhsTemplateArgs;
if (const auto *TST = RhsType->getAs<TemplateSpecializationType>()) {
Expand All @@ -2791,6 +2810,24 @@ void DeclareImplicitDeductionGuidesForTypeAlias(
} else {
assert(false && "unhandled RHS type of the alias");
}
return {Template, AliasRhsTemplateArgs};
}

// Build deduction guides for a type alias template.
void DeclareImplicitDeductionGuidesForTypeAlias(
Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate, SourceLocation Loc) {
if (AliasTemplate->isInvalidDecl())
return;
auto &Context = SemaRef.Context;
// FIXME: if there is an explicit deduction guide after the first use of the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What needs to happen to fix this? This is a decent sized issue. Is this covered under UB (of so, can we diagnose that?), or does it have well defined rules?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior depends on where we see the first usage that triggers alias CTAD in the TU. The basic rule is "well-defined":

  • when we encounter the first usage (let's say alias Bar), we will generate all deduction guides for the Bar.
  • for all the following usages of the Bar, we reuse these generated deduction guides above;
    If there are some explicit deduction guides after the first usage, they are not covered and are not added to the overload candidate sets.

Regarding the fix, I haven't put much thought into it yet. A naive solution would be that we cache these results (underlying deduction guides -> alias deduction guides), and check that if we have generated the alias deduction guides.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a diagnostic on an explicit deduction guide used after first use? If they are not covered/added to the candidate set, we should diagnose that it isn't valid (then, anything AFTER the error where we generate them 'wrong' is irrelevant anyway).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emitting a diagnostic is an option, but I think it's suboptimal, and it is not trivial to detect such cases. I prefer to fix the issue directly rather than implementing a diagnostic that would eventually be removed.

Do you consider this case to be critical for the clang trunk at the moment? I believe we should fix it before the next clang release, and we still have some time to do so.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WOULD we remove said diagnostic? It seems that:
If there are some explicit deduction guides after the first usage, they are not covered and are not added to the overload candidate sets.

means that adding an explicit deduction guide after first use effectively 'does nothing'.

That said, I'm ok having us do it (or whatever we do next) in a followup patch.

// type alias usage, we will not cover this explicit deduction guide. fix this
// case.
if (hasDeclaredDeductionGuides(
Context.DeclarationNames.getCXXDeductionGuideName(AliasTemplate),
AliasTemplate->getDeclContext()))
return;
auto [Template, AliasRhsTemplateArgs] =
getRHSTemplateDeclAndArgs(SemaRef, AliasTemplate);
if (!Template)
return;
DeclarationNameInfo NameInfo(
Expand All @@ -2803,6 +2840,13 @@ void DeclareImplicitDeductionGuidesForTypeAlias(
FunctionTemplateDecl *F = dyn_cast<FunctionTemplateDecl>(G);
if (!F)
continue;
// The **aggregate** deduction guides are handled in a different code path
// (DeclareImplicitDeductionGuideFromInitList), which involves the tricky
// cache.
if (cast<CXXDeductionGuideDecl>(F->getTemplatedDecl())
->getDeductionCandidateKind() == DeductionCandidate::Aggregate)
continue;

auto RType = F->getTemplatedDecl()->getReturnType();
// The (trailing) return type of the deduction guide.
const TemplateSpecializationType *FReturnType =
Expand Down Expand Up @@ -2885,21 +2929,6 @@ void DeclareImplicitDeductionGuidesForTypeAlias(
// parameters, used for building `TemplateArgsForBuildingFPrime`.
SmallVector<TemplateArgument, 16> TransformedDeducedAliasArgs(
AliasTemplate->getTemplateParameters()->size());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this was just moved... I'd still love to see us figure out WHEN this could be nullptr. I fear this is a case where we are going to 'miss' something if we add a new template param type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is reasonable that some of it elements will be nullptr, because not all template parameters of the alias template will be rewritten.

I fear this is a case where we are going to 'miss' something if we add a new template param type.

For this particular case, we have covered it in transformTemplateParameter by adding the llvm_unreachable as per your other comment.

auto TransformTemplateParameter =
[&SemaRef](DeclContext *DC, NamedDecl *TemplateParam,
MultiLevelTemplateArgumentList &Args,
unsigned NewIndex) -> NamedDecl * {
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
return transformTemplateTypeParam(SemaRef, DC, TTP, Args,
TTP->getDepth(), NewIndex);
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex,
TTP->getDepth());
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex,
NTTP->getDepth());
return nullptr;
};

for (unsigned AliasTemplateParamIdx : DeducedAliasTemplateParams) {
auto *TP = AliasTemplate->getTemplateParameters()->getParam(
Expand All @@ -2909,9 +2938,9 @@ void DeclareImplicitDeductionGuidesForTypeAlias(
MultiLevelTemplateArgumentList Args;
Args.setKind(TemplateSubstitutionKind::Rewrite);
Args.addOuterTemplateArguments(TransformedDeducedAliasArgs);
NamedDecl *NewParam =
TransformTemplateParameter(AliasTemplate->getDeclContext(), TP, Args,
/*NewIndex*/ FPrimeTemplateParams.size());
NamedDecl *NewParam = transformTemplateParameter(
SemaRef, AliasTemplate->getDeclContext(), TP, Args,
/*NewIndex*/ FPrimeTemplateParams.size());
FPrimeTemplateParams.push_back(NewParam);

auto NewTemplateArgument = Context.getCanonicalTemplateArgument(
Expand All @@ -2927,8 +2956,8 @@ void DeclareImplicitDeductionGuidesForTypeAlias(
// We take a shortcut here, it is ok to reuse the
// TemplateArgsForBuildingFPrime.
Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime);
NamedDecl *NewParam = TransformTemplateParameter(
F->getDeclContext(), TP, Args, FPrimeTemplateParams.size());
NamedDecl *NewParam = transformTemplateParameter(
SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size());
FPrimeTemplateParams.push_back(NewParam);

assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() &&
Expand All @@ -2938,16 +2967,8 @@ void DeclareImplicitDeductionGuidesForTypeAlias(
Context.getInjectedTemplateArg(NewParam));
}
// Substitute new template parameters into requires-clause if present.
Expr *RequiresClause = nullptr;
if (Expr *InnerRC = F->getTemplateParameters()->getRequiresClause()) {
MultiLevelTemplateArgumentList Args;
Args.setKind(TemplateSubstitutionKind::Rewrite);
Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime);
ExprResult E = SemaRef.SubstExpr(InnerRC, Args);
if (E.isInvalid())
return;
RequiresClause = E.getAs<Expr>();
}
Expr *RequiresClause =
transformRequireClause(SemaRef, F, TemplateArgsForBuildingFPrime);
// FIXME: implement the is_deducible constraint per C++
// [over.match.class.deduct]p3.3:
// ... and a constraint that is satisfied if and only if the arguments
Expand Down Expand Up @@ -3013,11 +3034,102 @@ void DeclareImplicitDeductionGuidesForTypeAlias(
}
}

// Build an aggregate deduction guide for a type alias template.
FunctionTemplateDecl *DeclareAggregateDeductionGuideForTypeAlias(
Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate,
MutableArrayRef<QualType> ParamTypes, SourceLocation Loc) {
TemplateDecl *RHSTemplate =
getRHSTemplateDeclAndArgs(SemaRef, AliasTemplate).first;
if (!RHSTemplate)
return nullptr;
auto *RHSDeductionGuide = SemaRef.DeclareAggregateDeductionGuideFromInitList(
RHSTemplate, ParamTypes, Loc);
if (!RHSDeductionGuide)
return nullptr;

LocalInstantiationScope Scope(SemaRef);

// Build a new template parameter list for the synthesized aggregate deduction
// guide by transforming the one from RHSDeductionGuide.
SmallVector<NamedDecl *> TransformedTemplateParams;
// Template args that refer to the rebuilt template parameters.
// All template arguments must be initialized in advance.
SmallVector<TemplateArgument> TransformedTemplateArgs(
RHSDeductionGuide->getTemplateParameters()->size());
for (auto *TP : *RHSDeductionGuide->getTemplateParameters()) {
// Rebuild any internal references to earlier parameters and reindex as
// we go.
MultiLevelTemplateArgumentList Args;
Args.setKind(TemplateSubstitutionKind::Rewrite);
Args.addOuterTemplateArguments(TransformedTemplateArgs);
NamedDecl *NewParam = transformTemplateParameter(
SemaRef, AliasTemplate->getDeclContext(), TP, Args,
/*NewIndex=*/TransformedTemplateParams.size());

TransformedTemplateArgs[TransformedTemplateParams.size()] =
SemaRef.Context.getCanonicalTemplateArgument(
SemaRef.Context.getInjectedTemplateArg(NewParam));
TransformedTemplateParams.push_back(NewParam);
}
// FIXME: implement the is_deducible constraint per C++
// [over.match.class.deduct]p3.3.
Expr *TransformedRequiresClause = transformRequireClause(
SemaRef, RHSDeductionGuide, TransformedTemplateArgs);
auto *TransformedTemplateParameterList = TemplateParameterList::Create(
SemaRef.Context, AliasTemplate->getTemplateParameters()->getTemplateLoc(),
AliasTemplate->getTemplateParameters()->getLAngleLoc(),
TransformedTemplateParams,
AliasTemplate->getTemplateParameters()->getRAngleLoc(),
TransformedRequiresClause);
auto *TransformedTemplateArgList = TemplateArgumentList::CreateCopy(
SemaRef.Context, TransformedTemplateArgs);

if (auto *TransformedDeductionGuide = SemaRef.InstantiateFunctionDeclaration(
RHSDeductionGuide, TransformedTemplateArgList,
AliasTemplate->getLocation(),
Sema::CodeSynthesisContext::BuildingDeductionGuides)) {
auto *GD =
llvm::dyn_cast<clang::CXXDeductionGuideDecl>(TransformedDeductionGuide);
FunctionTemplateDecl *Result = buildDeductionGuide(
SemaRef, AliasTemplate, TransformedTemplateParameterList,
GD->getCorrespondingConstructor(), GD->getExplicitSpecifier(),
GD->getTypeSourceInfo(), AliasTemplate->getBeginLoc(),
AliasTemplate->getLocation(), AliasTemplate->getEndLoc(),
GD->isImplicit());
cast<CXXDeductionGuideDecl>(Result->getTemplatedDecl())
->setDeductionCandidateKind(DeductionCandidate::Aggregate);
return Result;
}
return nullptr;
}

} // namespace

FunctionTemplateDecl *Sema::DeclareImplicitDeductionGuideFromInitList(
FunctionTemplateDecl *Sema::DeclareAggregateDeductionGuideFromInitList(
TemplateDecl *Template, MutableArrayRef<QualType> ParamTypes,
SourceLocation Loc) {
llvm::FoldingSetNodeID ID;
ID.AddPointer(Template);
for (auto &T : ParamTypes)
T.getCanonicalType().Profile(ID);
unsigned Hash = ID.ComputeHash();

auto Found = AggregateDeductionCandidates.find(Hash);
if (Found != AggregateDeductionCandidates.end()) {
CXXDeductionGuideDecl *GD = Found->getSecond();
return GD->getDescribedFunctionTemplate();
}

if (auto *AliasTemplate = llvm::dyn_cast<TypeAliasTemplateDecl>(Template)) {
if (auto *FTD = DeclareAggregateDeductionGuideForTypeAlias(
*this, AliasTemplate, ParamTypes, Loc)) {
auto *GD = cast<CXXDeductionGuideDecl>(FTD->getTemplatedDecl());
GD->setDeductionCandidateKind(DeductionCandidate::Aggregate);
AggregateDeductionCandidates[Hash] = GD;
return FTD;
}
}

if (CXXRecordDecl *DefRecord =
cast<CXXRecordDecl>(Template->getTemplatedDecl())->getDefinition()) {
if (TemplateDecl *DescribedTemplate =
Expand Down Expand Up @@ -3050,10 +3162,13 @@ FunctionTemplateDecl *Sema::DeclareImplicitDeductionGuideFromInitList(
Transform.NestedPattern ? Transform.NestedPattern : Transform.Template;
ContextRAII SavedContext(*this, Pattern->getTemplatedDecl());

auto *DG = cast<FunctionTemplateDecl>(
auto *FTD = cast<FunctionTemplateDecl>(
Transform.buildSimpleDeductionGuide(ParamTypes));
SavedContext.pop();
return DG;
auto *GD = cast<CXXDeductionGuideDecl>(FTD->getTemplatedDecl());
GD->setDeductionCandidateKind(DeductionCandidate::Aggregate);
AggregateDeductionCandidates[Hash] = GD;
return FTD;
}

void Sema::DeclareImplicitDeductionGuides(TemplateDecl *Template,
Expand Down
12 changes: 12 additions & 0 deletions clang/test/SemaTemplate/deduction-guide.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,15 @@ G g = {1};
// CHECK: FunctionTemplateDecl
// CHECK: |-CXXDeductionGuideDecl {{.*}} implicit <deduction guide for G> 'auto (T) -> G<T>' aggregate
// CHECK: `-CXXDeductionGuideDecl {{.*}} implicit used <deduction guide for G> 'auto (int) -> G<int>' implicit_instantiation aggregate

template<typename X>
using AG = G<X>;
AG ag = {1};
// Verify that the aggregate deduction guide for alias templates is built.
// CHECK-LABEL: Dumping <deduction guide for AG>
// CHECK: FunctionTemplateDecl
// CHECK: |-CXXDeductionGuideDecl {{.*}} 'auto (type-parameter-0-0) -> G<type-parameter-0-0>'
// CHECK: `-CXXDeductionGuideDecl {{.*}} 'auto (int) -> G<int>' implicit_instantiation
// CHECK: |-TemplateArgument type 'int'
// CHECK: | `-BuiltinType {{.*}} 'int'
// CHECK: `-ParmVarDecl {{.*}} 'int'