From 2faed1c47d6990ad982c8fab2e16b86612a6e068 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Sat, 31 Dec 2016 21:41:23 +0000 Subject: [PATCH] [c++17] Implement P0522R0 as written. This allows a template template argument to be specified for a template template parameter whenever the parameter is at least as specialized as the argument (when there's an obvious and correct mapping from uses of the parameter to uses of the argument). For example, a template with more parameters can be passed to a template template parameter with fewer, if those trailing parameters have default arguments. This is disabled by default, despite being a DR resolution, as it's fairly broken in its current state: there are no partial ordering rules to cope with template template parameters that have different parameter lists, meaning that code that attempts to decompose template-ids based on arity can hit unavoidable ambiguity issues. The diagnostics produced on a non-matching argument are also pretty bad right now, but I aim to improve them in a subsequent commit. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@290792 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/LangOptions.def | 1 + include/clang/Driver/Options.td | 5 + include/clang/Sema/Sema.h | 3 + lib/Driver/Tools.cpp | 7 ++ lib/Frontend/CompilerInvocation.cpp | 2 + lib/Sema/SemaTemplate.cpp | 28 ++++- lib/Sema/SemaTemplateDeduction.cpp | 85 +++++++++++++-- test/SemaTemplate/temp_arg_template.cpp | 4 +- test/SemaTemplate/temp_arg_template_cxx1z.cpp | 102 ++++++++++++++++++ www/cxx_status.html | 10 +- 10 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 test/SemaTemplate/temp_arg_template_cxx1z.cpp diff --git a/include/clang/Basic/LangOptions.def b/include/clang/Basic/LangOptions.def index e65811b361..e10d5c474d 100644 --- a/include/clang/Basic/LangOptions.def +++ b/include/clang/Basic/LangOptions.def @@ -134,6 +134,7 @@ LANGOPT(NoBuiltin , 1, 0, "disable builtin functions") LANGOPT(NoMathBuiltin , 1, 0, "disable math builtin functions") LANGOPT(GNUAsm , 1, 1, "GNU-style inline assembly") LANGOPT(CoroutinesTS , 1, 0, "C++ coroutines TS") +LANGOPT(RelaxedTemplateTemplateArgs, 1, 0, "C++17 relaxed matching of tempalte template arguments") BENIGN_LANGOPT(ThreadsafeStatics , 1, 1, "thread-safe static initializers") LANGOPT(POSIXThreads , 1, 0, "POSIX thread support") diff --git a/include/clang/Driver/Options.td b/include/clang/Driver/Options.td index 09b15285f8..1f1222e106 100644 --- a/include/clang/Driver/Options.td +++ b/include/clang/Driver/Options.td @@ -1088,6 +1088,11 @@ def fapplication_extension : Flag<["-"], "fapplication-extension">, HelpText<"Restrict code to those available for App Extensions">; def fno_application_extension : Flag<["-"], "fno-application-extension">, Group; +def frelaxed_template_template_args : Flag<["-"], "frelaxed-template-template-args">, + Flags<[CC1Option]>, HelpText<"Enable C++17 relaxed template template argument matching">, + Group; +def fno_relaxed_template_template_args : Flag<["-"], "fno-relaxed-template-template-args">, + Group; def fsized_deallocation : Flag<["-"], "fsized-deallocation">, Flags<[CC1Option]>, HelpText<"Enable C++14 sized global deallocation functions">, Group; def fno_sized_deallocation: Flag<["-"], "fno-sized-deallocation">, Group; diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index d012ebdab2..82caaeb24a 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -6719,6 +6719,9 @@ public: bool isMoreSpecializedThanPrimary(VarTemplatePartialSpecializationDecl *T, sema::TemplateDeductionInfo &Info); + bool isTemplateTemplateParameterAtLeastAsSpecializedAs( + TemplateParameterList *P, TemplateDecl *AArg, SourceLocation Loc); + void MarkUsedTemplateParameters(const TemplateArgumentList &TemplateArgs, bool OnlyDeduced, unsigned Depth, diff --git a/lib/Driver/Tools.cpp b/lib/Driver/Tools.cpp index 8ed12d72b4..2a367bb29a 100644 --- a/lib/Driver/Tools.cpp +++ b/lib/Driver/Tools.cpp @@ -6020,6 +6020,13 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, options::OPT_fno_assume_sane_operator_new)) CmdArgs.push_back("-fno-assume-sane-operator-new"); + // -frelaxed-template-template-args is off by default, as it is a severe + // breaking change until a corresponding change to template partial ordering + // is provided. + if (Args.hasFlag(options::OPT_frelaxed_template_template_args, + options::OPT_fno_relaxed_template_template_args, false)) + CmdArgs.push_back("-frelaxed-template-template-args"); + // -fsized-deallocation is off by default, as it is an ABI-breaking change for // most platforms. if (Args.hasFlag(options::OPT_fsized_deallocation, diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index ae4417c93c..a0682e26e7 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -1960,6 +1960,8 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK, if (!Opts.NoBuiltin) getAllNoBuiltinFuncValues(Args, Opts.NoBuiltinFuncs); Opts.NoMathBuiltin = Args.hasArg(OPT_fno_math_builtin); + Opts.RelaxedTemplateTemplateArgs = + Args.hasArg(OPT_frelaxed_template_template_args); Opts.SizedDeallocation = Args.hasArg(OPT_fsized_deallocation); Opts.AlignedAllocation = Args.hasFlag(OPT_faligned_allocation, OPT_fno_aligned_allocation, diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index ec2f598814..facc5d1b37 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -5585,6 +5585,10 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, return Arg; } +static void DiagnoseTemplateParameterListArityMismatch( + Sema &S, TemplateParameterList *New, TemplateParameterList *Old, + Sema::TemplateParameterListEqualKind Kind, SourceLocation TemplateArgLoc); + /// \brief Check a template argument against its corresponding /// template template parameter. /// @@ -5601,6 +5605,9 @@ bool Sema::CheckTemplateArgument(TemplateTemplateParmDecl *Param, return false; } + if (Template->isInvalidDecl()) + return true; + // C++0x [temp.arg.template]p1: // A template-argument for a template template-parameter shall be // the name of a class template or an alias template, expressed as an @@ -5628,6 +5635,25 @@ bool Sema::CheckTemplateArgument(TemplateTemplateParmDecl *Param, if (Param->isExpandedParameterPack()) Params = Param->getExpansionTemplateParameters(ArgumentPackIndex); + // C++1z [temp.arg.template]p3: (DR 150) + // A template-argument matches a template template-parameter P when P + // is at least as specialized as the template-argument A. + if (getLangOpts().RelaxedTemplateTemplateArgs) { + // Quick check for the common case: + // If P contains a parameter pack, then A [...] matches P if each of A's + // template parameters matches the corresponding template parameter in + // the template-parameter-list of P. + if (TemplateParameterListsAreEqual( + Template->getTemplateParameters(), Params, false, + TPL_TemplateTemplateArgumentMatch, Arg.getLocation())) + return false; + + if (isTemplateTemplateParameterAtLeastAsSpecializedAs(Params, Template, + Arg.getLocation())) + return false; + // FIXME: Produce better diagnostics for deduction failures. + } + return !TemplateParameterListsAreEqual(Template->getTemplateParameters(), Params, true, @@ -5839,7 +5865,7 @@ static bool MatchTemplateParameterKind(Sema &S, NamedDecl *New, NamedDecl *Old, return false; } - // Check that both are parameter packs are neither are parameter packs. + // Check that both are parameter packs or neither are parameter packs. // However, if we are matching a template template argument to a // template template parameter, the template template parameter can have // a parameter pack where the template template argument does not. diff --git a/lib/Sema/SemaTemplateDeduction.cpp b/lib/Sema/SemaTemplateDeduction.cpp index df796072f5..68b9853bdf 100644 --- a/lib/Sema/SemaTemplateDeduction.cpp +++ b/lib/Sema/SemaTemplateDeduction.cpp @@ -1898,11 +1898,11 @@ DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams, return NumberOfArgumentsMustMatch ? Sema::TDK_TooFewArguments : Sema::TDK_Success; - if (Args[ArgIdx].isPackExpansion()) { - // FIXME: We follow the logic of C++0x [temp.deduct.type]p22 here, - // but applied to pack expansions that are template arguments. + // C++1z [temp.deduct.type]p9: + // During partial ordering, if Ai was originally a pack expansion [and] + // Pi is not a pack expansion, template argument deduction fails. + if (Args[ArgIdx].isPackExpansion()) return Sema::TDK_MiscellaneousDeductionFailure; - } // Perform deduction for this Pi/Ai pair. if (Sema::TemplateDeductionResult Result @@ -1965,7 +1965,8 @@ DeduceTemplateArguments(Sema &S, TemplateDeductionInfo &Info, SmallVectorImpl &Deduced) { return DeduceTemplateArguments(S, TemplateParams, ParamList.asArray(), - ArgList.asArray(), Info, Deduced, false); + ArgList.asArray(), Info, Deduced, + /*NumberOfArgumentsMustMatch*/false); } /// \brief Determine whether two template arguments are the same. @@ -4581,13 +4582,13 @@ UnresolvedSetIterator Sema::getMostSpecialized( /// Determine whether one partial specialization, P1, is at least as /// specialized than another, P2. /// -/// \tparam PartialSpecializationDecl The kind of P2, which must be a -/// {Class,Var}Template{PartialSpecialization,}Decl. +/// \tparam TemplateLikeDecl The kind of P2, which must be a +/// TemplateDecl or {Class,Var}TemplatePartialSpecializationDecl. /// \param T1 The injected-class-name of P1 (faked for a variable template). /// \param T2 The injected-class-name of P2 (faked for a variable template). -template +template static bool isAtLeastAsSpecializedAs(Sema &S, QualType T1, QualType T2, - PartialSpecializationDecl *P2, + TemplateLikeDecl *P2, TemplateDeductionInfo &Info) { // C++ [temp.class.order]p1: // For two class template partial specializations, the first is at least as @@ -4729,6 +4730,72 @@ bool Sema::isMoreSpecializedThanPrimary( return true; } +bool Sema::isTemplateTemplateParameterAtLeastAsSpecializedAs( + TemplateParameterList *P, TemplateDecl *AArg, SourceLocation Loc) { + // 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 + // function templates... + + // Rather than synthesize function templates, we merely perform the + // 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(); + + // - 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 + SmallVector AArgs; + Context.getInjectedTemplateArgs(A, AArgs); + + // Check P's arguments against A's parameter list. This will fill in default + // template arguments as needed. AArgs are already correct by construction. + // We can't just use CheckTemplateIdType because that will expand alias + // templates. + SmallVector PArgs; + { + SFINAETrap Trap(*this); + + Context.getInjectedTemplateArgs(P, PArgs); + TemplateArgumentListInfo PArgList(P->getLAngleLoc(), P->getRAngleLoc()); + for (unsigned I = 0, N = P->size(); I != N; ++I) { + // Unwrap packs that getInjectedTemplateArgs wrapped around pack + // expansions, to form an "as written" argument list. + TemplateArgument Arg = PArgs[I]; + if (Arg.getKind() == TemplateArgument::Pack) { + assert(Arg.pack_size() == 1 && Arg.pack_begin()->isPackExpansion()); + Arg = *Arg.pack_begin(); + } + PArgList.addArgument(getTrivialTemplateArgumentLoc( + Arg, QualType(), P->getParam(I)->getLocation())); + } + PArgs.clear(); + + // C++1z [temp.arg.template]p3: + // If the rewrite produces an invalid type, then P is not at least as + // specialized as A. + if (CheckTemplateArgumentList(AArg, Loc, PArgList, false, PArgs) || + Trap.hasErrorOccurred()) + return false; + } + + QualType AType = Context.getTemplateSpecializationType(X, AArgs); + QualType PType = Context.getTemplateSpecializationType(X, PArgs); + + 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); +} + static void MarkUsedTemplateParameters(ASTContext &Ctx, const TemplateArgument &TemplateArg, diff --git a/test/SemaTemplate/temp_arg_template.cpp b/test/SemaTemplate/temp_arg_template.cpp index 6d93f1504a..67cde53c92 100644 --- a/test/SemaTemplate/temp_arg_template.cpp +++ b/test/SemaTemplate/temp_arg_template.cpp @@ -6,11 +6,12 @@ template class X> struct A; // expected-note 2{{previous te template class X> struct B; // expected-note{{previous template template parameter is here}} -template class X> struct C; // expected-note{{previous non-type template parameter with type 'int' is here}} +template class X> struct C; // expected-note 2{{previous non-type template parameter with type 'int' is here}} template struct X; // expected-note{{too few template parameters in template template argument}} template struct Y; // expected-note{{template parameter has a different kind in template argument}} template struct Ylong; // expected-note{{template non-type parameter has a different type 'long' in template argument}} +template struct Yref; // expected-note{{template non-type parameter has a different type 'const int &' in template argument}} namespace N { template struct Z; @@ -27,6 +28,7 @@ A *a5; // expected-error{{template template argument has different temp B *a6; // expected-error{{template template argument has different template parameters than its corresponding template template parameter}} C *a7; C *a8; // expected-error{{template template argument has different template parameters than its corresponding template template parameter}} +C *a9; // expected-error{{template template argument has different template parameters than its corresponding template template parameter}} template void f(int); diff --git a/test/SemaTemplate/temp_arg_template_cxx1z.cpp b/test/SemaTemplate/temp_arg_template_cxx1z.cpp new file mode 100644 index 0000000000..b6b283b53c --- /dev/null +++ b/test/SemaTemplate/temp_arg_template_cxx1z.cpp @@ -0,0 +1,102 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++1z -frelaxed-template-template-args %s + +// expected-note@temp_arg_template_cxx1z.cpp:* 1+{{}} + +template typename> struct Ti; +template typename> struct TPi; +template typename> struct TiPi; +template typename> struct TPiPi; // FIXME: Why is this not ill-formed? + +template typename> struct tT0; +template typename> struct Tt0; + +template typename> struct Tt; +template typename> struct TtPt; + +template struct i; +template struct iDi; +template struct ii; +template struct Pi; +template struct iiPi; + +template struct iDt; +template struct it; + +template struct t0; + +template struct Pt; + +namespace IntParam { + using ok = Pt, + Ti, + Ti, + Ti>; + using err1 = Ti; // expected-error {{different template parameters}} + using err2 = Ti; // expected-error {{different template parameters}} + using err3 = Ti; // expected-error {{different template parameters}} + using err4 = Ti; // expected-error {{different template parameters}} +} + +// These are accepted by the backwards-compatibility "parameter pack in +// parameter matches any number of parameters in arguments" rule. +namespace IntPackParam { + using ok = TPi; + using ok_compat = Pt, TPi, TPi, TPi>; + using err1 = TPi; // expected-error {{different template parameters}} + using err2 = TPi; // expected-error {{different template parameters}} + using err3 = TPi; // expected-error {{different template parameters}} +} + +namespace IntAndPackParam { + using ok = TiPi; + using ok_compat = Pt, TiPi, TiPi>; + using err = TiPi; +} + +namespace DependentType { + using ok = Pt, tT0>; + using err1 = tT0; // expected-error {{different template parameters}} + using err2 = tT0; // FIXME: should this be OK? + using err2a = tT0; // FIXME: should this be OK (if long long is larger than int)? + using err2b = tT0; // expected-error {{different template parameters}} + using err3 = tT0; // expected-error {{different template parameters}} + + using ok2 = Tt0; + using err4 = Tt0; // expected-error {{different template parameters}} +} + +namespace Auto { + template typename T> struct TInt {}; + template typename T> struct TIntPtr {}; + template typename T> struct TAuto {}; + template typename T> struct TAutoPtr {}; + template struct Auto; + template struct AutoPtr; + template struct Int; + template struct IntPtr; + + TInt ia; + TInt iap; // FIXME: ill-formed + TInt ii; + TInt iip; // expected-error {{different template parameters}} + + TIntPtr ipa; + TIntPtr ipap; + TIntPtr ipi; // expected-error {{different template parameters}} + TIntPtr ipip; + + TAuto aa; + TAuto aap; // FIXME: ill-formed + TAuto ai; // FIXME: ill-formed + TAuto aip; // FIXME: ill-formed + + TAutoPtr apa; + TAutoPtr apap; + TAutoPtr api; // FIXME: ill-formed + TAutoPtr apip; // FIXME: ill-formed + + int n; + template struct SubstFailure; + TInt isf; // expected-error {{different template parameters}} + TIntPtr ipsf; // expected-error {{different template parameters}} +} diff --git a/www/cxx_status.html b/www/cxx_status.html index 4a9236a014..675cc6f7a8 100644 --- a/www/cxx_status.html +++ b/www/cxx_status.html @@ -733,7 +733,7 @@ as the draft C++1z standard evolves. Matching template template parameters to compatible arguments P0522R0 - No (12) + Partial (12) Removing deprecated dynamic exception specifications @@ -763,8 +763,12 @@ left to right in the callee. As a result, function parameters in calls to functions using expression syntax are no longer guaranteed to be destroyed in reverse construction order in that ABI.
-(12): This is the resolution to a Defect Report, so will be -applied to all language versions. +(12): Despite being the the resolution to a Defect Report, this +feature is disabled by default in all language versions, and can be enabled +explicitly with the flag -frelaxed-template-template-args. The change +to the standard lacks a corresponding change for template partial ordering, +resulting in ambiguity errors for reasonable and previously-valid code. This +issue is expected to be rectified soon.

-- 2.40.0