Skip to content

Commit

Permalink
[clang] Implement CWG2398 provisional TTP matching to class templates
Browse files Browse the repository at this point in the history
This solves some ambuguity introduced in P0522 regarding how
template template parameters are partially ordered, and should reduce
the negative impact of enabling `-frelaxed-template-template-args`
by default.

When performing template argument deduction, we extend the provisional
wording introduced in #89807
so it also covers deduction of class templates.

Given the following example:
```C++
template <class T1, class T2 = float> struct A;
template <class T3> struct B;

template <template <class T4> class TT1, class T5> struct B<TT1<T5>>;   // #1
template <class T6, class T7>                      struct B<A<T6, T7>>; // #2

template struct B<A<int>>;
```
Prior to P0522, `#2` was picked. Afterwards, this became ambiguous.
This patch restores the pre-P0522 behavior, `#2` is picked again.

This has the beneficial side effect of making the following code valid:
```C++
template<class T, class U> struct A {};
A<int, float> v;
template<template<class> class TT> void f(TT<int>);

// OK: TT picks 'float' as the default argument for the second parameter.
void g() { f(v); }
```

---

Since this changes provisional implementation of CWG2398 which has
not been released yet, and already contains a changelog entry,
we don't provide a changelog entry here.
  • Loading branch information
mizvekov committed May 22, 2024
1 parent 229cb63 commit 73d456c
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 36 deletions.
5 changes: 4 additions & 1 deletion clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,8 @@ static void SetNestedNameSpecifier(Sema &S, TagDecl *T,
// Returns the template parameter list with all default template argument
// information.
static TemplateParameterList *GetTemplateParameterList(TemplateDecl *TD) {
if (TD->isImplicit())
return TD->getTemplateParameters();
// Make sure we get the template parameter list from the most
// recent declaration, since that is the only one that is guaranteed to
// have all the default template argument information.
Expand All @@ -1827,7 +1829,8 @@ static TemplateParameterList *GetTemplateParameterList(TemplateDecl *TD) {
// template <class = void> friend struct C;
// };
// template struct S<int>;
while (D->getFriendObjectKind() != Decl::FriendObjectKind::FOK_None &&
while ((D->isImplicit() ||
D->getFriendObjectKind() != Decl::FriendObjectKind::FOK_None) &&
D->getPreviousDecl())
D = D->getPreviousDecl();
return cast<TemplateDecl>(D)->getTemplateParameters();
Expand Down
76 changes: 46 additions & 30 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,8 @@ static NamedDecl *getTemplateParameterWithDefault(Sema &S, NamedDecl *A,
R->setDefaultArgument(
S.Context,
S.getTrivialTemplateArgumentLoc(Default, QualType(), SourceLocation()));
if (R->hasTypeConstraint()) {
auto *C = R->getTypeConstraint();
if (T->hasTypeConstraint()) {
auto *C = T->getTypeConstraint();
R->setTypeConstraint(C->getConceptReference(),
C->getImmediatelyDeclaredConstraint());
}
Expand Down Expand Up @@ -583,37 +583,53 @@ DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams,
return TemplateDeductionResult::Success;

auto NewDeduced = DeducedTemplateArgument(Arg);
// Provisional resolution for CWG2398: If Arg is also a template template
// param, and it names a template specialization, then we deduce a
// synthesized template template parameter based on A, but using the TS's
// arguments as defaults.
if (auto *TempArg = dyn_cast_or_null<TemplateTemplateParmDecl>(
Arg.getAsTemplateDecl())) {
// Provisional resolution for CWG2398: If Arg names a template
// specialization, then we deduce a synthesized template template parameter
// based on A, but using the TS's arguments as defaults.
if (DefaultArguments.size() != 0) {
assert(Arg.getKind() == TemplateName::Template);
assert(!TempArg->isExpandedParameterPack());

TemplateDecl *TempArg = Arg.getAsTemplateDecl();
TemplateParameterList *As = TempArg->getTemplateParameters();
if (DefaultArguments.size() != 0) {
assert(DefaultArguments.size() <= As->size());
SmallVector<NamedDecl *, 4> Params(As->size());
for (unsigned I = 0; I < DefaultArguments.size(); ++I)
Params[I] = getTemplateParameterWithDefault(S, As->getParam(I),
DefaultArguments[I]);
for (unsigned I = DefaultArguments.size(); I < As->size(); ++I)
Params[I] = As->getParam(I);
// FIXME: We could unique these, and also the parameters, but we don't
// expect programs to contain a large enough amount of these deductions
// for that to be worthwhile.
auto *TPL = TemplateParameterList::Create(
S.Context, SourceLocation(), SourceLocation(), Params,
SourceLocation(), As->getRequiresClause());
NewDeduced = DeducedTemplateArgument(
TemplateName(TemplateTemplateParmDecl::Create(
S.Context, TempArg->getDeclContext(), SourceLocation(),
TempArg->getDepth(), TempArg->getPosition(),
TempArg->isParameterPack(), TempArg->getIdentifier(),
TempArg->wasDeclaredWithTypename(), TPL)));
assert(DefaultArguments.size() <= As->size());

SmallVector<NamedDecl *, 4> Params(As->size());
for (unsigned I = 0; I < DefaultArguments.size(); ++I)
Params[I] = getTemplateParameterWithDefault(S, As->getParam(I),
DefaultArguments[I]);
for (unsigned I = DefaultArguments.size(); I < As->size(); ++I)
Params[I] = As->getParam(I);
// FIXME: We could unique these, and also the parameters, but we don't
// expect programs to contain a large enough amount of these deductions
// for that to be worthwhile.
auto *TPL = TemplateParameterList::Create(
S.Context, SourceLocation(), SourceLocation(), Params,
SourceLocation(), As->getRequiresClause());

TemplateDecl *TD;
switch (TempArg->getKind()) {
case Decl::TemplateTemplateParm: {
auto *A = cast<TemplateTemplateParmDecl>(TempArg);
assert(!A->isExpandedParameterPack());
TD = TemplateTemplateParmDecl::Create(
S.Context, A->getDeclContext(), SourceLocation(), A->getDepth(),
A->getPosition(), A->isParameterPack(), A->getIdentifier(),
A->wasDeclaredWithTypename(), TPL);
break;
}
case Decl::ClassTemplate: {
auto *A = cast<ClassTemplateDecl>(TempArg);
auto *CT = ClassTemplateDecl::Create(S.Context, A->getDeclContext(),
SourceLocation(), A->getDeclName(),
TPL, A->getTemplatedDecl());
CT->setPreviousDecl(A);
TD = CT;
break;
}
default:
llvm_unreachable("Unexpected Template Kind");
}
TD->setImplicit(true);
NewDeduced = DeducedTemplateArgument(TemplateName(TD));
}

DeducedTemplateArgument Result = checkDeducedTemplateArguments(S.Context,
Expand Down
5 changes: 3 additions & 2 deletions clang/test/CXX/temp/temp.decls/temp.alias/p2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ namespace StdExample {
{ /* ... */ }

template<template<class> class TT>
void f(TT<int>); // expected-note {{candidate template ignored}}
void f(TT<int>);

template<template<class,class> class TT>
void g(TT<int, Alloc<int>>);

int h() {
f(v); // expected-error {{no matching function for call to 'f'}}
f(v); // OK: TT = vector, Alloc<int> is used as the default argument for the
// second parameter.
g(v); // OK: TT = vector
}

Expand Down
3 changes: 0 additions & 3 deletions clang/test/SemaTemplate/cwg2398.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,10 @@ namespace class_template {
template <class T3> struct B;

template <template <class T4> class TT1, class T5> struct B<TT1<T5>>;
// new-note@-1 {{partial specialization matches}}

template <class T6, class T7> struct B<A<T6, T7>> {};
// new-note@-1 {{partial specialization matches}}

template struct B<A<int>>;
// new-error@-1 {{ambiguous partial specialization}}
} // namespace class_template

namespace type_pack1 {
Expand Down

0 comments on commit 73d456c

Please sign in to comment.