From 39e0af93163068f8de190649eccf91fda84178b6 Mon Sep 17 00:00:00 2001 From: Matheus Izvekov Date: Wed, 1 May 2024 22:29:45 -0300 Subject: [PATCH] [clang] Implement provisional wording for CWG2398 regarding packs 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 struct A; template class TT1, class T4> struct A>; // #1 template class TT2, class T6> struct A>; // #2 template struct B; template struct A>; ``` 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. --- clang/include/clang/Sema/Sema.h | 5 +- clang/lib/Sema/SemaTemplate.cpp | 10 ++-- clang/lib/Sema/SemaTemplateDeduction.cpp | 67 +++++++++++++++++++----- clang/test/SemaTemplate/cwg2398.cpp | 15 ++---- 4 files changed, 65 insertions(+), 32 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6a414aa57f32be..7d7eb6c5591106 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -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 ParamRange = {}); @@ -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); diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index c7aac068e264b7..116d1ac077b29e 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -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()); @@ -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) { @@ -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 diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index b5d405111fe4cb..c8319b8a4bc517 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -139,13 +139,15 @@ static TemplateDeductionResult DeduceTemplateArgumentsByTypeMatch( SmallVectorImpl &Deduced, unsigned TDF, bool PartialOrdering = false, bool DeducedFromArrayBound = false); +enum class PackFold { ParameterToArgument, ArgumentToParameter }; static TemplateDeductionResult DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams, ArrayRef Ps, ArrayRef As, TemplateDeductionInfo &Info, SmallVectorImpl &Deduced, - bool NumberOfArgumentsMustMatch); + bool NumberOfArgumentsMustMatch, + PackFold PackFold = PackFold::ParameterToArgument); static void MarkUsedTemplateParameters(ASTContext &Ctx, const TemplateArgument &TemplateArg, @@ -2550,7 +2552,9 @@ DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams, ArrayRef As, TemplateDeductionInfo &Info, SmallVectorImpl &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 @@ -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; @@ -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; @@ -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 @@ -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 @@ -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 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 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 { diff --git a/clang/test/SemaTemplate/cwg2398.cpp b/clang/test/SemaTemplate/cwg2398.cpp index d163354b2e5fed..31686c4bc98050 100644 --- a/clang/test/SemaTemplate/cwg2398.cpp +++ b/clang/test/SemaTemplate/cwg2398.cpp @@ -62,25 +62,19 @@ namespace templ { namespace type_pack1 { template struct A; template class TT1, class T4> struct A> ; - // new-note@-1 {{partial specialization matches}} template class TT2, class T6> struct A> {}; - // new-note@-1 {{partial specialization matches}} template struct B; template struct A>; - // new-error@-1 {{ambiguous partial specialization}} } // namespace type_pack1 namespace type_pack2 { template struct A; template class TT1, class ...T4> struct A> ; - // new-note@-1 {{partial specialization matches}} template class TT2, class ...T6> struct A> {}; - // new-note@-1 {{partial specialization matches}} template struct B; template struct A>; - // new-error@-1 {{ambiguous partial specialization}} } // namespace type_pack2 namespace type_pack3 { @@ -140,13 +134,10 @@ namespace ttp_defaults { namespace ttp_only { template