Skip to content

Commit

Permalink
[clang] Implement provisional wording for CWG2398 regarding packs
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, a template template parameter
containing no packs should be more specialized than one that does.

Given the following example:
```C++
template<class T2> struct A;
template<template<class ...T3s> class TT1, class T4> struct A<TT1<T4>>; // #1
template<template<class    T5 > class TT2, class T6> struct A<TT2<T6>>; // #2

template<class T1> struct B;
template struct A<B<char>>;
```

Prior to P0522, candidate #2 would be more specialized.
After P0522, neither is more specialized, so this becomes ambiguous.
With this change, #2 becomes more specialized again,
maintaining compatibility with pre-P0522 implementations.

The problem is that in P0522, candidates are at least as specialized
when matching packs to fixed-size lists both ways, whereas before,
a fixed-size list is more specialized.

This patch keeps the original behavior when checking template arguments
outside deduction, but restores this aspect of pre-P0522 matching
during deduction.

---

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 16, 2024
1 parent 70a926c commit 39e0af9
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 32 deletions.
5 changes: 3 additions & 2 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -9133,7 +9133,7 @@ class Sema final : public SemaBase {
CheckTemplateArgumentKind CTAK);
bool CheckTemplateTemplateArgument(TemplateTemplateParmDecl *Param,
TemplateParameterList *Params,
TemplateArgumentLoc &Arg);
TemplateArgumentLoc &Arg, bool IsDeduced);

void NoteTemplateLocation(const NamedDecl &Decl,
std::optional<SourceRange> ParamRange = {});
Expand Down Expand Up @@ -9603,7 +9603,8 @@ class Sema final : public SemaBase {
sema::TemplateDeductionInfo &Info);

bool isTemplateTemplateParameterAtLeastAsSpecializedAs(
TemplateParameterList *PParam, TemplateDecl *AArg, SourceLocation Loc);
TemplateParameterList *PParam, TemplateDecl *AArg, SourceLocation Loc,
bool IsDeduced);

void MarkUsedTemplateParameters(const Expr *E, bool OnlyDeduced,
unsigned Depth, llvm::SmallBitVector &Used);
Expand Down
10 changes: 6 additions & 4 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6484,7 +6484,8 @@ bool Sema::CheckTemplateArgument(

case TemplateArgument::Template:
case TemplateArgument::TemplateExpansion:
if (CheckTemplateTemplateArgument(TempParm, Params, Arg))
if (CheckTemplateTemplateArgument(TempParm, Params, Arg,
/*IsDeduced=*/CTAK != CTAK_Specified))
return true;

SugaredConverted.push_back(Arg.getArgument());
Expand Down Expand Up @@ -8402,7 +8403,8 @@ static void DiagnoseTemplateParameterListArityMismatch(
/// It returns true if an error occurred, and false otherwise.
bool Sema::CheckTemplateTemplateArgument(TemplateTemplateParmDecl *Param,
TemplateParameterList *Params,
TemplateArgumentLoc &Arg) {
TemplateArgumentLoc &Arg,
bool IsDeduced) {
TemplateName Name = Arg.getArgument().getAsTemplateOrTemplatePattern();
TemplateDecl *Template = Name.getAsTemplateDecl();
if (!Template) {
Expand Down Expand Up @@ -8454,8 +8456,8 @@ bool Sema::CheckTemplateTemplateArgument(TemplateTemplateParmDecl *Param,
!Template->hasAssociatedConstraints())
return false;

if (isTemplateTemplateParameterAtLeastAsSpecializedAs(Params, Template,
Arg.getLocation())) {
if (isTemplateTemplateParameterAtLeastAsSpecializedAs(
Params, Template, Arg.getLocation(), IsDeduced)) {
// P2113
// C++20[temp.func.order]p2
// [...] If both deductions succeed, the partial ordering selects the
Expand Down
67 changes: 53 additions & 14 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,15 @@ static TemplateDeductionResult DeduceTemplateArgumentsByTypeMatch(
SmallVectorImpl<DeducedTemplateArgument> &Deduced, unsigned TDF,
bool PartialOrdering = false, bool DeducedFromArrayBound = false);

enum class PackFold { ParameterToArgument, ArgumentToParameter };
static TemplateDeductionResult
DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams,
ArrayRef<TemplateArgument> Ps,
ArrayRef<TemplateArgument> As,
TemplateDeductionInfo &Info,
SmallVectorImpl<DeducedTemplateArgument> &Deduced,
bool NumberOfArgumentsMustMatch);
bool NumberOfArgumentsMustMatch,
PackFold PackFold = PackFold::ParameterToArgument);

static void MarkUsedTemplateParameters(ASTContext &Ctx,
const TemplateArgument &TemplateArg,
Expand Down Expand Up @@ -2550,7 +2552,9 @@ DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams,
ArrayRef<TemplateArgument> As,
TemplateDeductionInfo &Info,
SmallVectorImpl<DeducedTemplateArgument> &Deduced,
bool NumberOfArgumentsMustMatch) {
bool NumberOfArgumentsMustMatch, PackFold PackFold) {
if (PackFold == PackFold::ArgumentToParameter)
std::swap(Ps, As);
// C++0x [temp.deduct.type]p9:
// If the template argument list of P contains a pack expansion that is not
// the last template argument, the entire template argument list is a
Expand Down Expand Up @@ -2581,8 +2585,11 @@ DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams,
return TemplateDeductionResult::MiscellaneousDeductionFailure;

// Perform deduction for this Pi/Ai pair.
if (auto Result = DeduceTemplateArguments(S, TemplateParams, P,
As[ArgIdx], Info, Deduced);
TemplateArgument Pi = P, Ai = As[ArgIdx];
if (PackFold == PackFold::ArgumentToParameter)
std::swap(Pi, Ai);
if (auto Result =
DeduceTemplateArguments(S, TemplateParams, Pi, Ai, Info, Deduced);
Result != TemplateDeductionResult::Success)
return Result;

Expand All @@ -2609,9 +2616,12 @@ DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams,
for (; hasTemplateArgumentForDeduction(As, ArgIdx) &&
PackScope.hasNextElement();
++ArgIdx) {
TemplateArgument Pi = Pattern, Ai = As[ArgIdx];
if (PackFold == PackFold::ArgumentToParameter)
std::swap(Pi, Ai);
// Deduce template arguments from the pattern.
if (auto Result = DeduceTemplateArguments(S, TemplateParams, Pattern,
As[ArgIdx], Info, Deduced);
if (auto Result =
DeduceTemplateArguments(S, TemplateParams, Pi, Ai, Info, Deduced);
Result != TemplateDeductionResult::Success)
return Result;

Expand Down Expand Up @@ -6213,7 +6223,8 @@ bool Sema::isMoreSpecializedThanPrimary(
}

bool Sema::isTemplateTemplateParameterAtLeastAsSpecializedAs(
TemplateParameterList *P, TemplateDecl *AArg, SourceLocation Loc) {
TemplateParameterList *P, TemplateDecl *AArg, SourceLocation Loc,
bool IsDeduced) {
// C++1z [temp.arg.template]p4: (DR 150)
// A template template-parameter P is at least as specialized as a
// template template-argument A if, given the following rewrite to two
Expand All @@ -6223,11 +6234,10 @@ bool Sema::isTemplateTemplateParameterAtLeastAsSpecializedAs(
// equivalent partial ordering by performing deduction directly on
// the template parameter lists of the template template parameters.
//
// Given an invented class template X with the template parameter list of
// A (including default arguments):
TemplateName X = Context.getCanonicalTemplateName(TemplateName(AArg));
TemplateParameterList *A = AArg->getTemplateParameters();

// Given an invented class template X with the template parameter list of
// A (including default arguments):
// - Each function template has a single function parameter whose type is
// a specialization of X with template arguments corresponding to the
// template parameters from the respective function template
Expand Down Expand Up @@ -6270,14 +6280,43 @@ bool Sema::isTemplateTemplateParameterAtLeastAsSpecializedAs(
return false;
}

QualType AType = Context.getCanonicalTemplateSpecializationType(X, AArgs);
QualType PType = Context.getCanonicalTemplateSpecializationType(X, PArgs);
// Determine whether P1 is at least as specialized as P2.
TemplateDeductionInfo Info(Loc, A->getDepth());
SmallVector<DeducedTemplateArgument, 4> Deduced;
Deduced.resize(A->size());

// ... the function template corresponding to P is at least as specialized
// as the function template corresponding to A according to the partial
// ordering rules for function templates.
TemplateDeductionInfo Info(Loc, A->getDepth());
return isAtLeastAsSpecializedAs(*this, PType, AType, AArg, Info);

// Provisional resolution for CWG2398: Regarding temp.arg.template]p4, when
// applying the partial ordering rules for function templates on
// the rewritten template template parameters:
// - In a deduced context, the matching of packs versus fixed-size needs to
// be inverted between Ps and As. On non-deduced context, matching needs to
// happen both ways, according to [temp.arg.template]p3, but this is
// currently implemented as a special case elsewhere.
if (::DeduceTemplateArguments(*this, A, AArgs, PArgs, Info, Deduced,
/*NumberOfArgumentsMustMatch=*/false,
IsDeduced ? PackFold::ArgumentToParameter
: PackFold::ParameterToArgument) !=
TemplateDeductionResult::Success)
return false;

SmallVector<TemplateArgument, 4> DeducedArgs(Deduced.begin(), Deduced.end());
Sema::InstantiatingTemplate Inst(*this, Info.getLocation(), AArg, DeducedArgs,
Info);
if (Inst.isInvalid())
return false;

bool AtLeastAsSpecialized;
runWithSufficientStackSpace(Info.getLocation(), [&] {
AtLeastAsSpecialized =
::FinishTemplateArgumentDeduction(
*this, AArg, /*IsPartialOrdering=*/true, PArgs, Deduced, Info) ==
TemplateDeductionResult::Success;
});
return AtLeastAsSpecialized;
}

namespace {
Expand Down
15 changes: 3 additions & 12 deletions clang/test/SemaTemplate/cwg2398.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,19 @@ namespace templ {
namespace type_pack1 {
template<class T2> struct A;
template<template<class ...T3s> class TT1, class T4> struct A<TT1<T4>> ;
// new-note@-1 {{partial specialization matches}}
template<template<class T5 > class TT2, class T6> struct A<TT2<T6>> {};
// new-note@-1 {{partial specialization matches}}

template<class T1> struct B;
template struct A<B<char>>;
// new-error@-1 {{ambiguous partial specialization}}
} // namespace type_pack1

namespace type_pack2 {
template<class T2> struct A;
template<template<class ...T3s> class TT1, class ...T4> struct A<TT1<T4...>> ;
// new-note@-1 {{partial specialization matches}}
template<template<class T5 > class TT2, class ...T6> struct A<TT2<T6...>> {};
// new-note@-1 {{partial specialization matches}}

template<class T1> struct B;
template struct A<B<char>>;
// new-error@-1 {{ambiguous partial specialization}}
} // namespace type_pack2

namespace type_pack3 {
Expand Down Expand Up @@ -140,13 +134,10 @@ namespace ttp_defaults {

namespace ttp_only {
template <template <class... > class TT1> struct A { static constexpr int V = 0; };
// new-note@-1 2{{template is declared here}}
template <template <class > class TT2> struct A<TT2> { static constexpr int V = 1; };
// new-error@-1 {{not more specialized than the primary template}}
// new-note@-2 {{partial specialization matches}}
// new-note@-1 {{partial specialization matches}}
template <template <class, class> class TT3> struct A<TT3> { static constexpr int V = 2; };
// new-error@-1 {{not more specialized than the primary template}}
// new-note@-2 {{partial specialization matches}}
// new-note@-1 {{partial specialization matches}}

template <class ... > struct B;
template <class > struct C;
Expand Down Expand Up @@ -193,5 +184,5 @@ namespace consistency {

template struct A<B<int>, B<int>, B<int>>;
// new-error@-1 {{ambiguous partial specializations}}
} // namespace t1
} // namespace t2
} // namespace consistency

0 comments on commit 39e0af9

Please sign in to comment.