Skip to content

Commit

Permalink
[clang] Implement provisional wording for CWG2398 regarding packs (ll…
Browse files Browse the repository at this point in the history
…vm#90820)

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>>; // llvm#2

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

Prior to P0522, candidate `llvm#2` would be more specialized.
After P0522, neither is more specialized, so this becomes ambiguous.
With this change, `llvm#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 authored May 16, 2024
1 parent 997eae3 commit c86a53d
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 @@ -9612,7 +9612,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 @@ -6554,7 +6554,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 @@ -8472,7 +8473,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 @@ -8524,8 +8526,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 @@ -6299,7 +6309,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 @@ -6309,11 +6320,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 @@ -6356,14 +6366,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 c86a53d

Please sign in to comment.