From 8e0726136235f153877c6c887e008f9ca1ac61a6 Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Fri, 3 Feb 2012 07:34:46 +0000 Subject: [PATCH] Implement support for a pack expansion into a fixed-length template. Such pack expansions can easily fail at template instantiation time, if the expanded parameter packs are of the wrong length. Fixes , PR9021, and the example that came up today at Going Native. llvm-svn: 149685 --- clang/lib/Sema/SemaTemplate.cpp | 146 ++++++++++++------ .../temp.variadic/fixed-expansion.cpp | 120 ++++++++++++++ 2 files changed, 221 insertions(+), 45 deletions(-) create mode 100644 clang/test/CXX/temp/temp.decls/temp.variadic/fixed-expansion.cpp diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 322e47321552..7cd604d58537 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -1887,7 +1887,6 @@ void Sema::NoteAllFoundTemplates(TemplateName Name) { } } - QualType Sema::CheckTemplateIdType(TemplateName Name, SourceLocation TemplateLoc, TemplateArgumentListInfo &TemplateArgs) { @@ -1923,9 +1922,6 @@ QualType Sema::CheckTemplateIdType(TemplateName Name, false, Converted)) return QualType(); - assert((Converted.size() == Template->getTemplateParameters()->size()) && - "Converted template argument list is too short!"); - QualType CanonType; bool InstantiationDependent = false; @@ -2866,6 +2862,29 @@ bool Sema::CheckTemplateArgument(NamedDecl *Param, return false; } +/// \brief Diagnose an arity mismatch in the +static bool diagnoseArityMismatch(Sema &S, TemplateDecl *Template, + SourceLocation TemplateLoc, + TemplateArgumentListInfo &TemplateArgs) { + TemplateParameterList *Params = Template->getTemplateParameters(); + unsigned NumParams = Params->size(); + unsigned NumArgs = TemplateArgs.size(); + + SourceRange Range; + if (NumArgs > NumParams) + Range = SourceRange(TemplateArgs[NumParams].getLocation(), + TemplateArgs.getRAngleLoc()); + S.Diag(TemplateLoc, diag::err_template_arg_list_different_arity) + << (NumArgs > NumParams) + << (isa(Template)? 0 : + isa(Template)? 1 : + isa(Template)? 2 : 3) + << Template << Range; + S.Diag(Template->getLocation(), diag::note_template_decl_here) + << Params->getSourceRange(); + return true; +} + /// \brief Check that the given template argument list is well-formed /// for specializing the given template. bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, @@ -2883,26 +2902,6 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, bool HasParameterPack = NumParams > 0 && Params->getParam(NumParams - 1)->isTemplateParameterPack(); - if ((NumArgs > NumParams && !HasParameterPack) || - (NumArgs < Params->getMinRequiredArguments() && - !PartialTemplateArgs)) { - // FIXME: point at either the first arg beyond what we can handle, - // or the '>', depending on whether we have too many or too few - // arguments. - SourceRange Range; - if (NumArgs > NumParams) - Range = SourceRange(TemplateArgs[NumParams].getLocation(), RAngleLoc); - Diag(TemplateLoc, diag::err_template_arg_list_different_arity) - << (NumArgs > NumParams) - << (isa(Template)? 0 : - isa(Template)? 1 : - isa(Template)? 2 : 3) - << Template << Range; - Diag(Template->getLocation(), diag::note_template_decl_here) - << Params->getSourceRange(); - Invalid = true; - } - // C++ [temp.arg]p1: // [...] The type and form of each template-argument specified in // a template-id shall match the type and form specified for the @@ -2914,10 +2913,12 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, ParamEnd = Params->end(); unsigned ArgIdx = 0; LocalInstantiationScope InstScope(*this, true); + bool SawPackExpansion = false; while (Param != ParamEnd) { if (ArgIdx < NumArgs) { // If we have an expanded parameter pack, make sure we don't have too // many arguments. + // FIXME: This really should fall out from the normal arity checking. if (NonTypeTemplateParmDecl *NTTP = dyn_cast(*Param)) { if (NTTP->isExpandedParameterPack() && @@ -2951,6 +2952,15 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, // Move to the next template parameter. ++Param; } + + // If this template argument is a pack expansion, record that fact + // and break out; we can't actually check any more. + if (TemplateArgs[ArgIdx].getArgument().isPackExpansion()) { + SawPackExpansion = true; + ++ArgIdx; + break; + } + ++ArgIdx; continue; } @@ -2970,7 +2980,7 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, if ((*Param)->isTemplateParameterPack()) break; - // We have a default template argument that we will use. + // Check whether we have a default argument. TemplateArgumentLoc Arg; // Retrieve the default template argument from the template @@ -2979,10 +2989,9 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, // (when the template parameter was part of a nested template) into // the default argument. if (TemplateTypeParmDecl *TTP = dyn_cast(*Param)) { - if (!TTP->hasDefaultArgument()) { - assert(Invalid && "Missing default argument"); - break; - } + if (!TTP->hasDefaultArgument()) + return diagnoseArityMismatch(*this, Template, TemplateLoc, + TemplateArgs); TypeSourceInfo *ArgType = SubstDefaultTemplateArgument(*this, Template, @@ -2997,10 +3006,9 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, ArgType); } else if (NonTypeTemplateParmDecl *NTTP = dyn_cast(*Param)) { - if (!NTTP->hasDefaultArgument()) { - assert(Invalid && "Missing default argument"); - break; - } + if (!NTTP->hasDefaultArgument()) + return diagnoseArityMismatch(*this, Template, TemplateLoc, + TemplateArgs); ExprResult E = SubstDefaultTemplateArgument(*this, Template, TemplateLoc, @@ -3016,10 +3024,9 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, TemplateTemplateParmDecl *TempParm = cast(*Param); - if (!TempParm->hasDefaultArgument()) { - assert(Invalid && "Missing default argument"); - break; - } + if (!TempParm->hasDefaultArgument()) + return diagnoseArityMismatch(*this, Template, TemplateLoc, + TemplateArgs); NestedNameSpecifierLoc QualifierLoc; TemplateName Name = SubstDefaultTemplateArgument(*this, Template, @@ -3057,11 +3064,65 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, ++ArgIdx; } + // If we saw a pack expansion, then directly convert the remaining arguments, + // because we don't know what parameters they'll match up with. + if (SawPackExpansion) { + bool AddToArgumentPack + = Param != ParamEnd && (*Param)->isTemplateParameterPack(); + while (ArgIdx < NumArgs) { + if (AddToArgumentPack) + ArgumentPack.push_back(TemplateArgs[ArgIdx].getArgument()); + else + Converted.push_back(TemplateArgs[ArgIdx].getArgument()); + ++ArgIdx; + } + + // Push the argument pack onto the list of converted arguments. + if (AddToArgumentPack) { + if (ArgumentPack.empty()) + Converted.push_back(TemplateArgument(0, 0)); + else { + Converted.push_back( + TemplateArgument::CreatePackCopy(Context, + ArgumentPack.data(), + ArgumentPack.size())); + ArgumentPack.clear(); + } + } + + return Invalid; + } + + // If we have any leftover arguments, then there were too many arguments. + // Complain and fail. + if (ArgIdx < NumArgs) + return diagnoseArityMismatch(*this, Template, TemplateLoc, TemplateArgs); + + // If we have an expanded parameter pack, make sure we don't have too + // many arguments. + // FIXME: This really should fall out from the normal arity checking. + if (Param != ParamEnd) { + if (NonTypeTemplateParmDecl *NTTP + = dyn_cast(*Param)) { + if (NTTP->isExpandedParameterPack() && + ArgumentPack.size() < NTTP->getNumExpansionTypes()) { + Diag(TemplateLoc, diag::err_template_arg_list_different_arity) + << false + << (isa(Template)? 0 : + isa(Template)? 1 : + isa(Template)? 2 : 3) + << Template; + Diag(Template->getLocation(), diag::note_template_decl_here) + << Params->getSourceRange(); + return true; + } + } + } + // Form argument packs for each of the parameter packs remaining. while (Param != ParamEnd) { // If we're checking a partial list of template arguments, don't fill // in arguments for non-template parameter packs. - if ((*Param)->isTemplateParameterPack()) { if (!HasParameterPack) return true; @@ -3073,7 +3134,8 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, ArgumentPack.size())); ArgumentPack.clear(); } - } + } else if (!PartialTemplateArgs) + return diagnoseArityMismatch(*this, Template, TemplateLoc, TemplateArgs); ++Param; } @@ -4961,9 +5023,6 @@ Sema::ActOnClassTemplateSpecialization(Scope *S, unsigned TagSpec, TemplateArgs, false, Converted)) return true; - assert((Converted.size() == ClassTemplate->getTemplateParameters()->size()) && - "Converted template argument list is too short!"); - // Find the class template (partial) specialization declaration that // corresponds to these arguments. if (isPartialSpecialization) { @@ -5955,9 +6014,6 @@ Sema::ActOnExplicitInstantiation(Scope *S, TemplateArgs, false, Converted)) return true; - assert((Converted.size() == ClassTemplate->getTemplateParameters()->size()) && - "Converted template argument list is too short!"); - // Find the class template specialization declaration that // corresponds to these arguments. void *InsertPos = 0; diff --git a/clang/test/CXX/temp/temp.decls/temp.variadic/fixed-expansion.cpp b/clang/test/CXX/temp/temp.decls/temp.variadic/fixed-expansion.cpp new file mode 100644 index 000000000000..5db21570aaf7 --- /dev/null +++ b/clang/test/CXX/temp/temp.decls/temp.variadic/fixed-expansion.cpp @@ -0,0 +1,120 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s + +template struct pair { }; +template struct tuple { }; + +template +struct is_same { + static const bool value = false; +}; + +template +struct is_same { + static const bool value = true; +}; + +namespace ExpandIntoFixed { + template, + typename W = V*> + class X0 { }; + + template + class X1 { + public: + typedef X0 type; + }; + + static_assert(is_same::type, + X0, pair*>>::value, + "fails with two default arguments"); + + static_assert(is_same::type, + X0>::value, + "fails with one default argument"); + + static_assert(is_same::type, + X0>::value, + "fails with no default arguments"); +} + +namespace ExpandIntoFixedShifted { + template, + typename W = V*> + class X0 { }; + + template + class X1 { + public: + typedef X0 type; + }; + + static_assert(is_same::type, + X0, pair*>>::value, + "fails with two default arguments"); + + static_assert(is_same::type, + X0>::value, + "fails with one default argument"); + + static_assert(is_same::type, + X0>::value, + "fails with no default arguments"); +} + +namespace Deduction { + template struct Foo {}; + template tuple &foo(Foo); + + void call_foo(Foo foo_if, Foo foo_i) { + tuple &t1 = foo(foo_if); + tuple &t2 = foo(foo_i); + } +} + +namespace PR9021a { + template + struct A { }; + + template + struct B { + A a1; + }; + + void test() { + B c; + } +} + +namespace PR9021b { + template + struct t2 + { + + }; + + template class M> + struct m + { + template + using inner = M; + }; + + m sta2; +} + +namespace PartialSpecialization { + template + struct X0; // expected-note{{template is declared here}} + + template + struct X0 { + }; + + X0 x0i; // expected-error{{too few template arguments for class template 'X0'}} + X0 x0if; + X0 x0ifd; +}