diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index e65811b36115..e10d5c474dc8 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/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/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 09b15285f8b3..1f1222e10636 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/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<f_Group>; +def frelaxed_template_template_args : Flag<["-"], "frelaxed-template-template-args">, + Flags<[CC1Option]>, HelpText<"Enable C++17 relaxed template template argument matching">, + Group<f_Group>; +def fno_relaxed_template_template_args : Flag<["-"], "fno-relaxed-template-template-args">, + Group<f_Group>; def fsized_deallocation : Flag<["-"], "fsized-deallocation">, Flags<[CC1Option]>, HelpText<"Enable C++14 sized global deallocation functions">, Group<f_Group>; def fno_sized_deallocation: Flag<["-"], "fno-sized-deallocation">, Group<f_Group>; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d012ebdab2ff..82caaeb24ae7 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/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/clang/lib/Driver/Tools.cpp b/clang/lib/Driver/Tools.cpp index 8ed12d72b408..2a367bb29aa5 100644 --- a/clang/lib/Driver/Tools.cpp +++ b/clang/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/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index ae4417c93c28..a0682e26e702 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/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/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index ec2f59881454..facc5d1b375b 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/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/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index df796072f597..68b9853bdf67 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/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<DeducedTemplateArgument> &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<typename PartialSpecializationDecl> +template<typename TemplateLikeDecl> 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<TemplateArgument, 8> 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<TemplateArgument, 4> 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<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); +} + static void MarkUsedTemplateParameters(ASTContext &Ctx, const TemplateArgument &TemplateArg, diff --git a/clang/test/SemaTemplate/temp_arg_template.cpp b/clang/test/SemaTemplate/temp_arg_template.cpp index 6d93f1504a46..67cde53c928b 100644 --- a/clang/test/SemaTemplate/temp_arg_template.cpp +++ b/clang/test/SemaTemplate/temp_arg_template.cpp @@ -6,11 +6,12 @@ template<template<typename T> class X> struct A; // expected-note 2{{previous te template<template<typename T, int I> class X> struct B; // expected-note{{previous template template parameter is here}} -template<template<int I> class X> struct C; // expected-note{{previous non-type template parameter with type 'int' is here}} +template<template<int I> class X> struct C; // expected-note 2{{previous non-type template parameter with type 'int' is here}} template<class> struct X; // expected-note{{too few template parameters in template template argument}} template<int N> struct Y; // expected-note{{template parameter has a different kind in template argument}} template<long N> struct Ylong; // expected-note{{template non-type parameter has a different type 'long' in template argument}} +template<const int &N> struct Yref; // expected-note{{template non-type parameter has a different type 'const int &' in template argument}} namespace N { template<class> struct Z; @@ -27,6 +28,7 @@ A<TooMany> *a5; // expected-error{{template template argument has different temp B<X> *a6; // expected-error{{template template argument has different template parameters than its corresponding template template parameter}} C<Y> *a7; C<Ylong> *a8; // expected-error{{template template argument has different template parameters than its corresponding template template parameter}} +C<Yref> *a9; // expected-error{{template template argument has different template parameters than its corresponding template template parameter}} template<typename T> void f(int); diff --git a/clang/test/SemaTemplate/temp_arg_template_cxx1z.cpp b/clang/test/SemaTemplate/temp_arg_template_cxx1z.cpp new file mode 100644 index 000000000000..b6b283b53c6b --- /dev/null +++ b/clang/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<template<int> typename> struct Ti; +template<template<int...> typename> struct TPi; +template<template<int, int...> typename> struct TiPi; +template<template<int..., int...> typename> struct TPiPi; // FIXME: Why is this not ill-formed? + +template<typename T, template<T> typename> struct tT0; +template<template<typename T, T> typename> struct Tt0; + +template<template<typename> typename> struct Tt; +template<template<typename, typename...> typename> struct TtPt; + +template<int> struct i; +template<int, int = 0> struct iDi; +template<int, int> struct ii; +template<int...> struct Pi; +template<int, int, int...> struct iiPi; + +template<int, typename = int> struct iDt; +template<int, typename> struct it; + +template<typename T, T v> struct t0; + +template<typename...> struct Pt; + +namespace IntParam { + using ok = Pt<Ti<i>, + Ti<iDi>, + Ti<Pi>, + Ti<iDt>>; + using err1 = Ti<ii>; // expected-error {{different template parameters}} + using err2 = Ti<iiPi>; // expected-error {{different template parameters}} + using err3 = Ti<t0>; // expected-error {{different template parameters}} + using err4 = Ti<it>; // 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<Pi>; + using ok_compat = Pt<TPi<i>, TPi<iDi>, TPi<ii>, TPi<iiPi>>; + using err1 = TPi<t0>; // expected-error {{different template parameters}} + using err2 = TPi<iDt>; // expected-error {{different template parameters}} + using err3 = TPi<it>; // expected-error {{different template parameters}} +} + +namespace IntAndPackParam { + using ok = TiPi<Pi>; + using ok_compat = Pt<TiPi<ii>, TiPi<iDi>, TiPi<iiPi>>; + using err = TiPi<iDi>; +} + +namespace DependentType { + using ok = Pt<tT0<int, i>, tT0<int, iDi>>; + using err1 = tT0<int, ii>; // expected-error {{different template parameters}} + using err2 = tT0<short, i>; // FIXME: should this be OK? + using err2a = tT0<long long, i>; // FIXME: should this be OK (if long long is larger than int)? + using err2b = tT0<void*, i>; // expected-error {{different template parameters}} + using err3 = tT0<short, t0>; // expected-error {{different template parameters}} + + using ok2 = Tt0<t0>; + using err4 = Tt0<it>; // expected-error {{different template parameters}} +} + +namespace Auto { + template<template<int> typename T> struct TInt {}; + template<template<int*> typename T> struct TIntPtr {}; + template<template<auto> typename T> struct TAuto {}; + template<template<auto*> typename T> struct TAutoPtr {}; + template<auto> struct Auto; + template<auto*> struct AutoPtr; + template<int> struct Int; + template<int*> struct IntPtr; + + TInt<Auto> ia; + TInt<AutoPtr> iap; // FIXME: ill-formed + TInt<Int> ii; + TInt<IntPtr> iip; // expected-error {{different template parameters}} + + TIntPtr<Auto> ipa; + TIntPtr<AutoPtr> ipap; + TIntPtr<Int> ipi; // expected-error {{different template parameters}} + TIntPtr<IntPtr> ipip; + + TAuto<Auto> aa; + TAuto<AutoPtr> aap; // FIXME: ill-formed + TAuto<Int> ai; // FIXME: ill-formed + TAuto<IntPtr> aip; // FIXME: ill-formed + + TAutoPtr<Auto> apa; + TAutoPtr<AutoPtr> apap; + TAutoPtr<Int> api; // FIXME: ill-formed + TAutoPtr<IntPtr> apip; // FIXME: ill-formed + + int n; + template<auto A, decltype(A) B = &n> struct SubstFailure; + TInt<SubstFailure> isf; // expected-error {{different template parameters}} + TIntPtr<SubstFailure> ipsf; // expected-error {{different template parameters}} +} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 4a9236a01460..675cc6f7a82b 100644 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -733,7 +733,7 @@ as the draft C++1z standard evolves. <tr> <td>Matching template template parameters to compatible arguments</td> <td><a href="http://wg21.link/p0522r0">P0522R0</a></td> - <td class="none" align="center">No <a href="#p0522">(12)</a></td> + <td class="partial" align="center">Partial <a href="#p0522">(12)</a></td> </tr> <tr> <td>Removing deprecated dynamic exception specifications</td> @@ -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. </span><br> -<span id="p0522">(12): This is the resolution to a Defect Report, so will be -applied to all language versions. +<span id="p0522">(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 <tt>-frelaxed-template-template-args</tt>. 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. </span> </p> </details>