[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.

llvm-svn: 290792
This commit is contained in:
Richard Smith 2016-12-31 21:41:23 +00:00
parent c8bf96182d
commit 26b86ea8b1
10 changed files with 233 additions and 14 deletions

View File

@ -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")

View File

@ -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>;

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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.

View File

@ -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,

View File

@ -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);

View File

@ -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}}
}

View File

@ -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>