From: Douglas Gregor Date: Tue, 31 Mar 2009 00:43:58 +0000 (+0000) Subject: Parsing and AST representation for dependent template names that occur X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c45c232440dfafedca1a3773b904fb42609b1b19;p=clang Parsing and AST representation for dependent template names that occur within nested-name-specifiers, e.g., for the "apply" in typename MetaFun::template apply::type At present, we can't instantiate these nested-name-specifiers, so our testing is sketchy. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@68081 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index f668ecd6d9..bad183df0a 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -699,6 +699,9 @@ def err_typename_nested_not_type : Error< def note_typename_refers_here : Note< "referenced member %0 is declared here">; +def err_template_kw_refers_to_non_template : Error< + "%0 following the 'template' keyword does not refer to a template">; + def err_unexpected_typedef : Error< "unexpected type name %0: expected expression">; def err_unexpected_namespace : Error< diff --git a/include/clang/Basic/TemplateKinds.h b/include/clang/Basic/TemplateKinds.h index dbaf5bdb60..c6ea05bb13 100644 --- a/include/clang/Basic/TemplateKinds.h +++ b/include/clang/Basic/TemplateKinds.h @@ -22,12 +22,14 @@ enum TemplateNameKind { /// The name refers to a function template or a set of overloaded /// functions that includes at least one function template. TNK_Function_template, - /// The name refers to a class template. - TNK_Class_template, - /// The name referes to a template template parameter. - TNK_Template_template_parm, - /// The name is dependent and is known to be a template name based - /// on syntax, e.g., "Alloc::template rebind". + /// The name refers to a template whose specialization produces a + /// type. The template itself could be a class template, template + /// template parameter, or C++0x template alias. + TNK_Type_template, + /// The name refers to a dependent template name. Whether the + /// template name is assumed to refer to a type template or a + /// function template depends on the context in which the template + /// name occurs. TNK_Dependent_template_name }; diff --git a/include/clang/Parse/Action.h b/include/clang/Parse/Action.h index f1837faac5..6186126e09 100644 --- a/include/clang/Parse/Action.h +++ b/include/clang/Parse/Action.h @@ -157,7 +157,7 @@ public: /// returned, and \p TemplateDecl receives the declaration. An /// optional CXXScope can be passed to indicate the C++ scope in /// which the identifier will be found. - virtual TemplateNameKind isTemplateName(IdentifierInfo &II, Scope *S, + virtual TemplateNameKind isTemplateName(const IdentifierInfo &II, Scope *S, TemplateTy &Template, const CXXScopeSpec *SS = 0) = 0; @@ -1229,6 +1229,20 @@ public: return TypeResult(); }; + /// \brief Form a dependent template name. + /// + /// This action forms a dependent template name given the template + /// name and its (presumably dependent) scope specifier. For + /// example, given "MetaFun::template apply", the scope specifier \p + /// SS will be "MetaFun::", \p TemplateKWLoc contains the location + /// of the "template" keyword, and "apply" is the \p Name. + virtual TemplateTy ActOnDependentTemplateName(SourceLocation TemplateKWLoc, + const IdentifierInfo &Name, + SourceLocation NameLoc, + const CXXScopeSpec &SS) { + return TemplateTy(); + } + /// \brief Process the declaration or definition of an explicit /// class template specialization or a class template partial /// specialization. @@ -1568,7 +1582,7 @@ public: virtual bool isCurrentClassName(const IdentifierInfo& II, Scope *S, const CXXScopeSpec *SS); - virtual TemplateNameKind isTemplateName(IdentifierInfo &II, Scope *S, + virtual TemplateNameKind isTemplateName(const IdentifierInfo &II, Scope *S, TemplateTy &Template, const CXXScopeSpec *SS = 0); diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index b62438cd61..18a3a4221a 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -1373,8 +1373,6 @@ ASTContext::getTemplateSpecializationType(TemplateName Template, const TemplateArgument *Args, unsigned NumArgs, QualType Canon) { - // FIXME: If Template is dependent, canonicalize it! - if (!Canon.isNull()) Canon = getCanonicalType(Canon); diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index 6922dcc6c0..a4117b2bdf 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -94,8 +94,12 @@ QualType Type::getDesugaredType() const { if (const TypeOfType *TOT = dyn_cast(this)) return TOT->getUnderlyingType().getDesugaredType(); if (const TemplateSpecializationType *Spec - = dyn_cast(this)) - return Spec->getCanonicalTypeInternal().getDesugaredType(); + = dyn_cast(this)) { + QualType Canon = Spec->getCanonicalTypeInternal(); + if (Canon->getAsTemplateSpecializationType()) + return QualType(this, 0); + return Canon->getDesugaredType(); + } if (const QualifiedNameType *QualName = dyn_cast(this)) return QualName->getNamedType().getDesugaredType(); diff --git a/lib/Parse/MinimalAction.cpp b/lib/Parse/MinimalAction.cpp index e99e96a7dc..fcbf4eea94 100644 --- a/lib/Parse/MinimalAction.cpp +++ b/lib/Parse/MinimalAction.cpp @@ -134,7 +134,7 @@ bool MinimalAction::isCurrentClassName(const IdentifierInfo &, Scope *, } TemplateNameKind -MinimalAction::isTemplateName(IdentifierInfo &II, Scope *S, +MinimalAction::isTemplateName(const IdentifierInfo &II, Scope *S, TemplateTy &TemplateDecl, const CXXScopeSpec *SS) { return TNK_Non_template; diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index f80afe3f4b..11658d4c50 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -513,7 +513,7 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, Token Next = NextToken(); if (Next.is(tok::annot_template_id) && static_cast(Next.getAnnotationValue()) - ->Kind == TNK_Class_template) { + ->Kind == TNK_Type_template) { // We have a qualified template-id, e.g., N::A CXXScopeSpec SS; ParseOptionalCXXScopeSpecifier(SS); @@ -640,7 +640,7 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, case tok::annot_template_id: { TemplateIdAnnotation *TemplateId = static_cast(Tok.getAnnotationValue()); - if (TemplateId->Kind != TNK_Class_template) { + if (TemplateId->Kind != TNK_Type_template) { // This template-id does not refer to a type name, so we're // done with the type-specifiers. goto DoneWithDeclSpec; diff --git a/lib/Parse/ParseDeclCXX.cpp b/lib/Parse/ParseDeclCXX.cpp index fdfc7315a8..663fea5096 100644 --- a/lib/Parse/ParseDeclCXX.cpp +++ b/lib/Parse/ParseDeclCXX.cpp @@ -307,7 +307,7 @@ Parser::TypeTy *Parser::ParseClassName(SourceLocation &EndLocation, if (Tok.is(tok::annot_template_id)) { TemplateIdAnnotation *TemplateId = static_cast(Tok.getAnnotationValue()); - if (TemplateId->Kind == TNK_Class_template) { + if (TemplateId->Kind == TNK_Type_template) { if (AnnotateTemplateIdTokenAsType(SS)) return 0; @@ -419,7 +419,7 @@ void Parser::ParseClassSpecifier(DeclSpec &DS, TemplateId = static_cast(Tok.getAnnotationValue()); NameLoc = ConsumeToken(); - if (TemplateId->Kind != TNK_Class_template) { + if (TemplateId->Kind != TNK_Type_template) { // The template-name in the simple-template-id refers to // something other than a class template. Give an appropriate // error message and skip to the ';'. diff --git a/lib/Parse/ParseExprCXX.cpp b/lib/Parse/ParseExprCXX.cpp index 4d419bbd87..2c6963a05f 100644 --- a/lib/Parse/ParseExprCXX.cpp +++ b/lib/Parse/ParseExprCXX.cpp @@ -92,9 +92,8 @@ bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS) { Tok.is(tok::kw_template)) { // Parse the optional 'template' keyword, then make sure we have // 'identifier <' after it. - SourceLocation TemplateKWLoc; if (Tok.is(tok::kw_template)) { - TemplateKWLoc = ConsumeToken(); + SourceLocation TemplateKWLoc = ConsumeToken(); if (Tok.isNot(tok::identifier)) { Diag(Tok.getLocation(), @@ -110,20 +109,20 @@ bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS) { << SourceRange(TemplateKWLoc, Tok.getLocation()); break; } - } - else { - // FIXME: If the nested-name-specifier thus far is dependent, - // we need to break out of here, because this '<' is taken as - // an operator and not as part of a simple-template-id. + + TemplateTy Template + = Actions.ActOnDependentTemplateName(TemplateKWLoc, + *Tok.getIdentifierInfo(), + Tok.getLocation(), + SS); + AnnotateTemplateIdToken(Template, TNK_Dependent_template_name, + &SS, TemplateKWLoc, false); + continue; } TemplateTy Template; - TemplateNameKind TNK = TNK_Non_template; - // FIXME: If the nested-name-specifier thus far is dependent, - // set TNK = TNK_Dependent_template_name and skip the - // "isTemplateName" check. - TNK = Actions.isTemplateName(*Tok.getIdentifierInfo(), - CurScope, Template, &SS); + TemplateNameKind TNK = Actions.isTemplateName(*Tok.getIdentifierInfo(), + CurScope, Template, &SS); if (TNK) { // We have found a template name, so annotate this this token // with a template-id annotation. We do not permit the @@ -131,7 +130,7 @@ bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS) { // because some clients (e.g., the parsing of class template // specializations) still want to see the original template-id // token. - AnnotateTemplateIdToken(Template, TNK, &SS, TemplateKWLoc, false); + AnnotateTemplateIdToken(Template, TNK, &SS, SourceLocation(), false); continue; } } @@ -142,12 +141,13 @@ bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS) { // simple-template-id '::' // // So we need to check whether the simple-template-id is of the - // right kind (it should name a type), and then convert it into - // a type within the nested-name-specifier. + // right kind (it should name a type or be dependent), and then + // convert it into a type within the nested-name-specifier. TemplateIdAnnotation *TemplateId = static_cast(Tok.getAnnotationValue()); - if (TemplateId->Kind == TNK_Class_template) { + if (TemplateId->Kind == TNK_Type_template || + TemplateId->Kind == TNK_Dependent_template_name) { if (AnnotateTemplateIdTokenAsType(&SS)) SS.clear(); @@ -172,7 +172,7 @@ bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS) { SS.setEndLoc(CCLoc); continue; } else - assert(false && "FIXME: Only class template names supported here"); + assert(false && "FIXME: Only type template names supported here"); } // We don't have any tokens that form the beginning of a diff --git a/lib/Parse/ParseTemplate.cpp b/lib/Parse/ParseTemplate.cpp index 8eda694864..45d148e38b 100644 --- a/lib/Parse/ParseTemplate.cpp +++ b/lib/Parse/ParseTemplate.cpp @@ -531,8 +531,7 @@ void Parser::AnnotateTemplateIdToken(TemplateTy Template, TemplateNameKind TNK, return; // Build the annotation token. - // FIXME: Not just for class templates! - if (TNK == TNK_Class_template && AllowTypeAnnotation) { + if (TNK == TNK_Type_template && AllowTypeAnnotation) { Action::TypeResult Type = Actions.ActOnTemplateIdType(Template, TemplateNameLoc, LAngleLoc, TemplateArgsPtr, @@ -550,8 +549,8 @@ void Parser::AnnotateTemplateIdToken(TemplateTy Template, TemplateNameKind TNK, else Tok.setLocation(TemplateNameLoc); } else { - // This is a function template. We'll be building a template-id - // annotation token. + // Build a template-id annotation token that can be processed + // later. Tok.setKind(tok::annot_template_id); TemplateIdAnnotation *TemplateId = TemplateIdAnnotation::Allocate(TemplateArgs.size()); @@ -595,8 +594,9 @@ bool Parser::AnnotateTemplateIdTokenAsType(const CXXScopeSpec *SS) { TemplateIdAnnotation *TemplateId = static_cast(Tok.getAnnotationValue()); - assert(TemplateId->Kind == TNK_Class_template && - "Only works for class templates"); + assert((TemplateId->Kind == TNK_Type_template || + TemplateId->Kind == TNK_Dependent_template_name) && + "Only works for type and dependent templates"); ASTTemplateArgsPtr TemplateArgsPtr(Actions, TemplateId->getTemplateArgs(), diff --git a/lib/Parse/Parser.cpp b/lib/Parse/Parser.cpp index a101aaa732..56e217a329 100644 --- a/lib/Parse/Parser.cpp +++ b/lib/Parse/Parser.cpp @@ -901,7 +901,7 @@ bool Parser::TryAnnotateTypeOrScopeToken() { if (Tok.is(tok::annot_template_id)) { TemplateIdAnnotation *TemplateId = static_cast(Tok.getAnnotationValue()); - if (TemplateId->Kind == TNK_Class_template) { + if (TemplateId->Kind == TNK_Type_template) { // A template-id that refers to a type was parsed into a // template-id annotation in a context where we weren't allowed // to produce a type annotation token. Update the template-id diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index c46b044c29..634c90a36e 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -1690,7 +1690,7 @@ public: //===--------------------------------------------------------------------===// // C++ Templates [C++ 14] // - virtual TemplateNameKind isTemplateName(IdentifierInfo &II, Scope *S, + virtual TemplateNameKind isTemplateName(const IdentifierInfo &II, Scope *S, TemplateTy &Template, const CXXScopeSpec *SS = 0); bool DiagnoseTemplateParameterShadow(SourceLocation Loc, Decl *PrevDecl); @@ -1756,6 +1756,11 @@ public: SourceLocation *TemplateArgLocs, SourceLocation RAngleLoc); + virtual TemplateTy ActOnDependentTemplateName(SourceLocation TemplateKWLoc, + const IdentifierInfo &Name, + SourceLocation NameLoc, + const CXXScopeSpec &SS); + bool CheckClassTemplateSpecializationScope(ClassTemplateDecl *ClassTemplate, ClassTemplateSpecializationDecl *PrevDecl, SourceLocation TemplateNameLoc, diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index 56a016d270..591f323347 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -26,7 +26,7 @@ using namespace clang; /// declaration if II names a template. An optional CXXScope can be /// passed to indicate the C++ scope in which the identifier will be /// found. -TemplateNameKind Sema::isTemplateName(IdentifierInfo &II, Scope *S, +TemplateNameKind Sema::isTemplateName(const IdentifierInfo &II, Scope *S, TemplateTy &TemplateResult, const CXXScopeSpec *SS) { NamedDecl *IIDecl = LookupParsedName(S, SS, &II, LookupOrdinaryName); @@ -38,10 +38,9 @@ TemplateNameKind Sema::isTemplateName(IdentifierInfo &II, Scope *S, if ((Template = dyn_cast(IIDecl))) { if (isa(IIDecl)) TNK = TNK_Function_template; - else if (isa(IIDecl)) - TNK = TNK_Class_template; - else if (isa(IIDecl)) - TNK = TNK_Template_template_parm; + else if (isa(IIDecl) || + isa(IIDecl)) + TNK = TNK_Type_template; else assert(false && "Unknown template declaration kind"); } else if (CXXRecordDecl *Record = dyn_cast(IIDecl)) { @@ -59,11 +58,11 @@ TemplateNameKind Sema::isTemplateName(IdentifierInfo &II, Scope *S, if (Record->isInjectedClassName()) { Record = cast(Context.getCanonicalDecl(Record)); if ((Template = Record->getDescribedClassTemplate())) - TNK = TNK_Class_template; + TNK = TNK_Type_template; else if (ClassTemplateSpecializationDecl *Spec = dyn_cast(Record)) { Template = Spec->getSpecializedTemplate(); - TNK = TNK_Class_template; + TNK = TNK_Type_template; } } } @@ -716,6 +715,56 @@ translateTemplateArguments(ASTTemplateArgsPtr &TemplateArgsIn, } } +/// \brief Build a canonical version of a template argument list. +/// +/// This function builds a canonical version of the given template +/// argument list, where each of the template arguments has been +/// converted into its canonical form. This routine is typically used +/// to canonicalize a template argument list when the template name +/// itself is dependent. When the template name refers to an actual +/// template declaration, Sema::CheckTemplateArgumentList should be +/// used to check and canonicalize the template arguments. +/// +/// \param TemplateArgs The incoming template arguments. +/// +/// \param NumTemplateArgs The number of template arguments in \p +/// TemplateArgs. +/// +/// \param Canonical A vector to be filled with the canonical versions +/// of the template arguments. +/// +/// \param Context The ASTContext in which the template arguments live. +static void CanonicalizeTemplateArguments(const TemplateArgument *TemplateArgs, + unsigned NumTemplateArgs, + llvm::SmallVectorImpl &Canonical, + ASTContext &Context) { + Canonical.reserve(NumTemplateArgs); + for (unsigned Idx = 0; Idx < NumTemplateArgs; ++Idx) { + switch (TemplateArgs[Idx].getKind()) { + case TemplateArgument::Expression: + // FIXME: Build canonical expression (!) + Canonical.push_back(TemplateArgs[Idx]); + break; + + case TemplateArgument::Declaration: + Canonical.push_back(TemplateArgument(SourceLocation(), + TemplateArgs[Idx].getAsDecl())); + break; + + case TemplateArgument::Integral: + Canonical.push_back(TemplateArgument(SourceLocation(), + *TemplateArgs[Idx].getAsIntegral(), + TemplateArgs[Idx].getIntegralType())); + + case TemplateArgument::Type: { + QualType CanonType + = Context.getCanonicalType(TemplateArgs[Idx].getAsType()); + Canonical.push_back(TemplateArgument(SourceLocation(), CanonType)); + } + } + } +} + QualType Sema::CheckTemplateIdType(TemplateName Name, SourceLocation TemplateLoc, SourceLocation LAngleLoc, @@ -723,7 +772,25 @@ QualType Sema::CheckTemplateIdType(TemplateName Name, unsigned NumTemplateArgs, SourceLocation RAngleLoc) { TemplateDecl *Template = Name.getAsTemplateDecl(); - assert(Template && "Cannot handle dependent template-names yet"); + if (!Template) { + // The template name does not resolve to a template, so we just + // build a dependent template-id type. + + // Canonicalize the template arguments to build the canonical + // template-id type. + llvm::SmallVector CanonicalTemplateArgs; + CanonicalizeTemplateArguments(TemplateArgs, NumTemplateArgs, + CanonicalTemplateArgs, Context); + + // FIXME: Get the canonical template-name + QualType CanonType + = Context.getTemplateSpecializationType(Name, &CanonicalTemplateArgs[0], + CanonicalTemplateArgs.size()); + + // Build the dependent template-id type. + return Context.getTemplateSpecializationType(Name, TemplateArgs, + NumTemplateArgs, CanonType); + } // Check that the template argument list is well-formed for this // template. @@ -808,6 +875,57 @@ Sema::ActOnTemplateIdType(TemplateTy TemplateD, SourceLocation TemplateLoc, return Result.getAsOpaquePtr(); } +/// \brief Form a dependent template name. +/// +/// This action forms a dependent template name given the template +/// name and its (presumably dependent) scope specifier. For +/// example, given "MetaFun::template apply", the scope specifier \p +/// SS will be "MetaFun::", \p TemplateKWLoc contains the location +/// of the "template" keyword, and "apply" is the \p Name. +Sema::TemplateTy +Sema::ActOnDependentTemplateName(SourceLocation TemplateKWLoc, + const IdentifierInfo &Name, + SourceLocation NameLoc, + const CXXScopeSpec &SS) { + if (!SS.isSet() || SS.isInvalid()) + return TemplateTy(); + + NestedNameSpecifier *Qualifier + = static_cast(SS.getScopeRep()); + + // FIXME: member of the current instantiation + + if (!Qualifier->isDependent()) { + // C++0x [temp.names]p5: + // If a name prefixed by the keyword template is not the name of + // a template, the program is ill-formed. [Note: the keyword + // template may not be applied to non-template members of class + // templates. -end note ] [ Note: as is the case with the + // typename prefix, the template prefix is allowed in cases + // where it is not strictly necessary; i.e., when the + // nested-name-specifier or the expression on the left of the -> + // or . is not dependent on a template-parameter, or the use + // does not appear in the scope of a template. -end note] + // + // Note: C++03 was more strict here, because it banned the use of + // the "template" keyword prior to a template-name that was not a + // dependent name. C++ DR468 relaxed this requirement (the + // "template" keyword is now permitted). We follow the C++0x + // rules, even in C++03 mode, retroactively applying the DR. + TemplateTy Template; + TemplateNameKind TNK = isTemplateName(Name, 0, Template, &SS); + if (TNK == TNK_Non_template) { + Diag(NameLoc, diag::err_template_kw_refers_to_non_template) + << &Name; + return TemplateTy(); + } + + return Template; + } + + return TemplateTy::make(Context.getDependentTemplateName(Qualifier, &Name)); +} + /// \brief Check that the given template argument list is well-formed /// for specializing the given template. bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, diff --git a/lib/Sema/SemaTemplateInstantiate.cpp b/lib/Sema/SemaTemplateInstantiate.cpp index 08f96012dd..2eb874597e 100644 --- a/lib/Sema/SemaTemplateInstantiate.cpp +++ b/lib/Sema/SemaTemplateInstantiate.cpp @@ -475,7 +475,7 @@ InstantiateTemplateSpecializationType( // FIXME: Need to instantiate into the template name. return SemaRef.CheckTemplateIdType(T->getTemplateName(), Loc, - SourceLocation(), + SourceLocation(), &InstantiatedTemplateArgs[0], InstantiatedTemplateArgs.size(), SourceLocation()); diff --git a/test/SemaTemplate/metafun-apply.cpp b/test/SemaTemplate/metafun-apply.cpp new file mode 100644 index 0000000000..22be5ab34f --- /dev/null +++ b/test/SemaTemplate/metafun-apply.cpp @@ -0,0 +1,29 @@ +// RUN: clang-cc -fsyntax-only %s + +struct add_pointer { + template + struct apply { + typedef T* type; + }; +}; + +struct add_reference { + template + struct apply { + typedef T& type; + }; +}; + +template +struct apply1 { + typedef typename MetaFun::template apply::type type; +}; + +#if 0 +// FIXME: The code below requires template instantiation for dependent +// template-names that occur within nested-name-specifiers. +int i; + +apply1::type ip = &i; +apply1::type ir = i; +#endif diff --git a/test/SemaTemplate/nested-name-spec-template.cpp b/test/SemaTemplate/nested-name-spec-template.cpp index f856268d6e..6df2ca6a91 100644 --- a/test/SemaTemplate/nested-name-spec-template.cpp +++ b/test/SemaTemplate/nested-name-spec-template.cpp @@ -42,8 +42,21 @@ namespace N { struct A { struct X; }; + + struct B; } struct ::N::A::X { int foo; }; + +#if 0 +// FIXME: the following crashes the parser, because Sema has no way to +// community that the "dependent" template-name N::template B doesn't +// actually refer to a template. +template +struct TestA { + typedef typename N::template B::type type; // xpected-error{{'B' following the 'template' keyword does not refer to a template}} + // FIXME: should show what B *does* refer to. +}; +#endif