[cfe-commits] r64848 - in /cfe/trunk: include/clang/AST/DeclBase.h include/clang/AST/DeclNodes.def include/clang/AST/DeclTemplate.h include/clang/Basic/DiagnosticParseKinds.def include/clang/Parse/Action.h include/clang/Parse/DeclSpec.h include/clang/Parse/Ownership.h include/clang/Parse/Parser.h lib/AST/DeclBase.cpp lib/AST/DeclTemplate.cpp lib/Parse/ParseDecl.cpp lib/Parse/ParseDeclCXX.cpp lib/Parse/ParseTemplate.cpp lib/Sema/Sema.h lib/Sema/SemaTemplate.cpp test/SemaTemplate/class-template-spec.cpp

Douglas Gregor dgregor at apple.com
Tue Feb 17 15:15:12 PST 2009


Author: dgregor
Date: Tue Feb 17 17:15:12 2009
New Revision: 64848

URL: http://llvm.org/viewvc/llvm-project?rev=64848&view=rev
Log:
Implement basic parsing and semantic analysis for explicit
specialization of class templates, e.g.,

  template<typename T> class X;

  template<> class X<int> { /* blah */ };

Each specialization is a different *Decl node (naturally), and can
have different members. We keep track of forward declarations and
definitions as for other class/struct/union types.

This is only the basic framework: we still have to deal with checking
the template headers properly, improving recovery when there are
failures, handling nested name specifiers, etc.


Added:
    cfe/trunk/test/SemaTemplate/class-template-spec.cpp
Modified:
    cfe/trunk/include/clang/AST/DeclBase.h
    cfe/trunk/include/clang/AST/DeclNodes.def
    cfe/trunk/include/clang/AST/DeclTemplate.h
    cfe/trunk/include/clang/Basic/DiagnosticParseKinds.def
    cfe/trunk/include/clang/Parse/Action.h
    cfe/trunk/include/clang/Parse/DeclSpec.h
    cfe/trunk/include/clang/Parse/Ownership.h
    cfe/trunk/include/clang/Parse/Parser.h
    cfe/trunk/lib/AST/DeclBase.cpp
    cfe/trunk/lib/AST/DeclTemplate.cpp
    cfe/trunk/lib/Parse/ParseDecl.cpp
    cfe/trunk/lib/Parse/ParseDeclCXX.cpp
    cfe/trunk/lib/Parse/ParseTemplate.cpp
    cfe/trunk/lib/Sema/Sema.h
    cfe/trunk/lib/Sema/SemaTemplate.cpp

Modified: cfe/trunk/include/clang/AST/DeclBase.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/DeclBase.h?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/include/clang/AST/DeclBase.h (original)
+++ cfe/trunk/include/clang/AST/DeclBase.h Tue Feb 17 17:15:12 2009
@@ -202,7 +202,7 @@
   bool isImplicit() const { return Implicit; }
   void setImplicit(bool I = true) { Implicit = I; }
   
-  IdentifierNamespace getIdentifierNamespace() const {
+  unsigned getIdentifierNamespace() const {
     switch (DeclKind) {
     default: 
       if (DeclKind >= FunctionFirst && DeclKind <= FunctionLast)
@@ -245,7 +245,10 @@
     case FunctionTemplate:
     case ClassTemplate:
     case TemplateTemplateParm:
-      return IdentifierNamespace(IDNS_Tag | IDNS_Ordinary);
+      return IDNS_Tag | IDNS_Ordinary;
+
+    case ClassTemplateSpecialization:
+      return 0;
     }
   }
   

Modified: cfe/trunk/include/clang/AST/DeclNodes.def
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/DeclNodes.def?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/include/clang/AST/DeclNodes.def (original)
+++ cfe/trunk/include/clang/AST/DeclNodes.def Tue Feb 17 17:15:12 2009
@@ -139,7 +139,7 @@
 DECL_RANGE(ObjCContainer, ObjCContainer, ObjCInterface)
 DECL_RANGE(Field, Field, ObjCAtDefsField)
 DECL_RANGE(Type, Typedef, TemplateTypeParm)
-DECL_RANGE(Tag, Enum, CXXRecord)
+DECL_RANGE(Tag, Enum, ClassTemplateSpecialization)
 DECL_RANGE(Record, Record, ClassTemplateSpecialization)
 DECL_RANGE(Value, EnumConstant, NonTypeTemplateParm)
 DECL_RANGE(Function, Function, CXXConversion)

Modified: cfe/trunk/include/clang/AST/DeclTemplate.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/DeclTemplate.h?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/include/clang/AST/DeclTemplate.h (original)
+++ cfe/trunk/include/clang/AST/DeclTemplate.h Tue Feb 17 17:15:12 2009
@@ -521,6 +521,23 @@
   }
 };
 
+// \brief Describes the kind of template specialization that a
+// particular template specialization declaration represents.
+enum TemplateSpecializationKind {
+  /// This template specialization was formed from a template-id but
+  /// has not yet been declared, defined, or instantiated.
+  TSK_Undeclared = 0,
+  /// This template specialization was declared or defined by an
+  /// explicit specialization (C++ [temp.expl.spec]).
+  TSK_ExplicitSpecialization,
+  /// This template specialization was implicitly instantiated from a
+  /// template. (C++ [temp.inst]).
+  TSK_ImplicitInstantiation,
+  /// This template specialization was instantiated from a template
+  /// due to an explicit instantiation request (C++ [temp.explicit]).
+  TSK_ExplicitInstantiation
+};
+
 /// \brief Represents a class template specialization, which refers to
 /// a class template with a given set of template arguments.
 ///
@@ -541,8 +558,12 @@
 
   /// \brief The number of template arguments. The actual arguments
   /// are allocated after the ClassTemplateSpecializationDecl object.
-  unsigned NumTemplateArgs;
-  
+  unsigned NumTemplateArgs : 16;
+
+  /// \brief The kind of specialization this declaration refers to.
+  /// Really a value of type TemplateSpecializationKind.
+  unsigned SpecializationKind : 2;
+
   ClassTemplateSpecializationDecl(DeclContext *DC, SourceLocation L,
                                   ClassTemplateDecl *SpecializedTemplate,
                                   TemplateArgument *TemplateArgs, 
@@ -552,7 +573,8 @@
   static ClassTemplateSpecializationDecl *
   Create(ASTContext &Context, DeclContext *DC, SourceLocation L,
          ClassTemplateDecl *SpecializedTemplate,
-         TemplateArgument *TemplateArgs, unsigned NumTemplateArgs);
+         TemplateArgument *TemplateArgs, unsigned NumTemplateArgs,
+         ClassTemplateSpecializationDecl *PrevDecl);
 
   /// \brief Retrieve the template that this specialization specializes.
   ClassTemplateDecl *getSpecializedTemplate() const { 
@@ -570,6 +592,16 @@
 
   unsigned getNumTemplateArgs() const { return NumTemplateArgs; }
 
+  /// \brief Determine the kind of specialization that this
+  /// declaration represents.
+  TemplateSpecializationKind getSpecializationKind() const {
+    return static_cast<TemplateSpecializationKind>(SpecializationKind);
+  }
+
+  void setSpecializationKind(TemplateSpecializationKind TSK) {
+    SpecializationKind = TSK;
+  }
+
   void Profile(llvm::FoldingSetNodeID &ID) const {
     Profile(ID, template_arg_begin(), getNumTemplateArgs());
   }

Modified: cfe/trunk/include/clang/Basic/DiagnosticParseKinds.def
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticParseKinds.def?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticParseKinds.def (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticParseKinds.def Tue Feb 17 17:15:12 2009
@@ -274,6 +274,8 @@
       "expected type-id after '%0'")
 DIAG(err_expected_class_before, ERROR,
       "expected 'class' before '%0'")
+DIAG(err_template_spec_syntax_non_template, ERROR,
+     "identifier followed by '<' indicates a class template specialization but %0 %select{does not refer to a template|refers to a function template|<unused>|refers to a template template parameter}1")
 
 // Language specific pragmas
 

Modified: cfe/trunk/include/clang/Parse/Action.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Parse/Action.h?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/include/clang/Parse/Action.h (original)
+++ cfe/trunk/include/clang/Parse/Action.h Tue Feb 17 17:15:12 2009
@@ -138,7 +138,7 @@
   /// isTemplateName.
   enum TemplateNameKind {
     /// The name does not refer to a template.
-    TNK_Non_template,
+    TNK_Non_template = 0,
     /// The name refers to a function template or a set of overloaded
     /// functions that includes at least one function template.
     TNK_Function_template,
@@ -1143,23 +1143,86 @@
   /// \brief Form a class template specialization from a template and
   /// a list of template arguments.
   ///
+  /// This action merely forms the type for the template-id, possibly
+  /// checking well-formedness of the template arguments. It does not
+  /// imply the declaration of any entity.
+  ///
   /// \param Template  A template whose specialization results in a
   /// type, e.g., a class template or template template parameter.
+  /// 
+  /// \param IsSpecialization true when we are naming the class
+  /// template specialization as part of an explicit class
+  /// specialization or class template partial specialization.
+  virtual TypeResult ActOnClassTemplateId(DeclTy *Template,
+                                          SourceLocation TemplateLoc,
+                                          SourceLocation LAngleLoc,
+                                          ASTTemplateArgsPtr TemplateArgs,
+                                          SourceLocation *TemplateArgLocs,
+                                          SourceLocation RAngleLoc,
+                                          const CXXScopeSpec *SS) {
+    return 0;
+  };
+
+  /// \brief Process the declaration or definition of an explicit
+  /// class template specialization or a class template partial
+  /// specialization.
+  ///
+  /// This routine is invoked when an explicit class template
+  /// specialization or a class template partial specialization is
+  /// declared or defined, to introduce the (partial) specialization
+  /// and produce a declaration for it. In the following example,
+  /// ActOnClassTemplateSpecialization will be invoked for the
+  /// declarations at both A and B:
+  ///
+  /// \code
+  /// template<typename T> class X;
+  /// template<> class X<int> { }; // A: explicit specialization
+  /// template<typename T> class X<T*> { }; // B: partial specialization
+  /// \endcode
+  ///
+  /// Note that it is the job of semantic analysis to determine which
+  /// of the two cases actually occurred in the source code, since
+  /// they are parsed through the same path. The formulation of the
+  /// template parameter lists describes which case we are in.
+  ///
+  /// \param S the current scope
   ///
-  /// \todo "Class template specialization" is the standard term for
-  /// the types that we're forming, but the name
-  /// ActOnClassTemplateSpecialization sounds like we're declaring a
-  /// new class template specialization.
-  virtual TypeTy * 
-  ActOnClassTemplateSpecialization(DeclTy *Template,
-                                   SourceLocation TemplateLoc,
+  /// \param TagSpec whether this declares a class, struct, or union
+  /// (template)
+  ///
+  /// \param TK whether this is a declaration or a definition
+  /// 
+  /// \param KWLoc the location of the 'class', 'struct', or 'union'
+  /// keyword.
+  ///
+  /// \param SS the scope specifier preceding the template-id
+  ///
+  /// \param Template the declaration of the class template that we
+  /// are specializing.
+  ///
+  /// \param Attr attributes on the specialization
+  ///
+  /// \param TemplateParameterLists the set of template parameter
+  /// lists that apply to this declaration. In a well-formed program,
+  /// the number of template parameter lists will be one more than the
+  /// number of template-ids in the scope specifier. However, it is
+  /// common for users to provide the wrong number of template
+  /// parameter lists (such as a missing \c template<> prior to a
+  /// specialization); the parser does not check this condition.
+  virtual DeclTy *
+  ActOnClassTemplateSpecialization(Scope *S, unsigned TagSpec, TagKind TK,
+                                   SourceLocation KWLoc, 
+                                   const CXXScopeSpec &SS,
+                                   DeclTy *Template,
+                                   SourceLocation TemplateNameLoc,
                                    SourceLocation LAngleLoc,
                                    ASTTemplateArgsPtr TemplateArgs,
                                    SourceLocation *TemplateArgLocs,
                                    SourceLocation RAngleLoc,
-                                   const CXXScopeSpec *SS = 0) {
+                                   AttributeList *Attr,
+                              MultiTemplateParamsArg TemplateParameterLists) {
     return 0;
-  };
+  }
 
   //===----------------------- Obj-C Declarations -------------------------===//
   

Modified: cfe/trunk/include/clang/Parse/DeclSpec.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Parse/DeclSpec.h?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/include/clang/Parse/DeclSpec.h (original)
+++ cfe/trunk/include/clang/Parse/DeclSpec.h Tue Feb 17 17:15:12 2009
@@ -995,7 +995,9 @@
   OverloadedOperatorKind getOverloadedOperator() const { return OperatorKind; }
 
   void setInvalidType(bool flag) { InvalidType = flag; }
-  bool getInvalidType() const { return InvalidType; }
+  bool getInvalidType() const { 
+    return InvalidType || DS.getTypeSpecType() == DeclSpec::TST_error; 
+  }
 
   void setGroupingParens(bool flag) { GroupingParens = flag; }
   bool hasGroupingParens() const { return GroupingParens; }

Modified: cfe/trunk/include/clang/Parse/Ownership.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Parse/Ownership.h?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/include/clang/Parse/Ownership.h (original)
+++ cfe/trunk/include/clang/Parse/Ownership.h Tue Feb 17 17:15:12 2009
@@ -571,6 +571,20 @@
 #endif
     }
 
+    // FIXME: Lame, not-fully-type-safe emulation of 'move semantics'.
+    ASTTemplateArgsPtr& operator=(ASTTemplateArgsPtr &Other)  {
+#if !defined(DISABLE_SMART_POINTERS)
+      Actions = Other.Actions;
+#endif
+      Args = Other.Args;
+      ArgIsType = Other.ArgIsType;
+      Count = Other.Count;
+#if !defined(DISABLE_SMART_POINTERS)
+      Other.Count = 0;
+#endif
+      return *this;
+    }
+
 #if !defined(DISABLE_SMART_POINTERS)
     ~ASTTemplateArgsPtr() { destroy(); }
 #endif
@@ -579,6 +593,16 @@
     bool *getArgIsType() const {return ArgIsType; }
     unsigned size() const { return Count; }
 
+    void reset(void **args, bool *argIsType, unsigned count) {
+      destroy();
+
+      Args = args;
+      ArgIsType = argIsType;
+      Count = count;
+    }
+
+    void *operator[](unsigned Arg) const { return Args[Arg]; }
+
     void **release() const { 
 #if !defined(DISABLE_SMART_POINTERS)
       Count = 0;

Modified: cfe/trunk/include/clang/Parse/Parser.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Parse/Parser.h?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/include/clang/Parse/Parser.h (original)
+++ cfe/trunk/include/clang/Parse/Parser.h Tue Feb 17 17:15:12 2009
@@ -996,7 +996,7 @@
   typedef Action::TemplateNameKind TemplateNameKind;
 
   // C++ 14.1: Template Parameters [temp.param]
-  DeclTy *ParseTemplateDeclaration(unsigned Context);
+  DeclTy *ParseTemplateDeclarationOrSpecialization(unsigned Context);
   bool ParseTemplateParameters(unsigned Depth, 
                                TemplateParameterList &TemplateParams,
                                SourceLocation &LAngleLoc, 
@@ -1011,6 +1011,17 @@
   typedef llvm::SmallVector<void *, 16> TemplateArgList;
   typedef llvm::SmallVector<bool, 16> TemplateArgIsTypeList;
   typedef llvm::SmallVector<SourceLocation, 16> TemplateArgLocationList;
+
+  bool ParseTemplateIdAfterTemplateName(DeclTy *Template,
+                                        SourceLocation TemplateNameLoc, 
+                                        const CXXScopeSpec *SS,
+                                        bool ConsumeLastToken,
+                                        SourceLocation &LAngleLoc,
+                                        TemplateArgList &TemplateArgs,
+                                    TemplateArgIsTypeList &TemplateArgIsType,
+                               TemplateArgLocationList &TemplateArgLocations,
+                                        SourceLocation &RAngleLoc);
+
   void AnnotateTemplateIdToken(DeclTy *Template, TemplateNameKind TNK,
                                const CXXScopeSpec *SS = 0);
   bool ParseTemplateArgumentList(TemplateArgList &TemplateArgs,

Modified: cfe/trunk/lib/AST/DeclBase.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/DeclBase.cpp?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/lib/AST/DeclBase.cpp (original)
+++ cfe/trunk/lib/AST/DeclBase.cpp Tue Feb 17 17:15:12 2009
@@ -319,17 +319,6 @@
     // The original namespace is our primary context.
     return static_cast<NamespaceDecl*>(this)->getOriginalNamespace();
 
-  case Decl::Enum:
-  case Decl::Record:
-  case Decl::CXXRecord:
-    // If this is a tag type that has a definition or is currently
-    // being defined, that definition is our primary context.
-    if (TagType *TagT = cast_or_null<TagType>(cast<TagDecl>(this)->TypeForDecl))
-      if (TagT->isBeingDefined() || 
-          (TagT->getDecl() && TagT->getDecl()->isDefinition()))
-        return TagT->getDecl();
-    return this;
-
   case Decl::ObjCMethod:
     return this;
 
@@ -344,6 +333,17 @@
     return this;
 
   default:
+    if (DeclKind >= Decl::TagFirst && DeclKind <= Decl::TagLast) {
+      // If this is a tag type that has a definition or is currently
+      // being defined, that definition is our primary context.
+      if (TagType *TagT 
+            = cast_or_null<TagType>(cast<TagDecl>(this)->TypeForDecl))
+        if (TagT->isBeingDefined() || 
+            (TagT->getDecl() && TagT->getDecl()->isDefinition()))
+          return TagT->getDecl();
+      return this;
+    }
+
     assert(DeclKind >= Decl::FunctionFirst && DeclKind <= Decl::FunctionLast &&
           "Unknown DeclContext kind");
     return this;
@@ -352,28 +352,11 @@
 
 DeclContext *DeclContext::getNextContext() {
   switch (DeclKind) {
-  case Decl::TranslationUnit:
-  case Decl::Enum:
-  case Decl::Record:
-  case Decl::CXXRecord:
-  case Decl::ObjCMethod:
-  case Decl::ObjCInterface:
-  case Decl::ObjCCategory:
-  case Decl::ObjCProtocol:
-  case Decl::ObjCImplementation:
-  case Decl::ObjCCategoryImpl:
-  case Decl::LinkageSpec:
-  case Decl::Block:
-    // There is only one DeclContext for these entities.
-    return 0;
-
   case Decl::Namespace:
     // Return the next namespace
     return static_cast<NamespaceDecl*>(this)->getNextNamespace();
 
   default:
-    assert(DeclKind >= Decl::FunctionFirst && DeclKind <= Decl::FunctionLast &&
-          "Unknown DeclContext kind");
     return 0;
   }
 }
@@ -463,6 +446,12 @@
 }
 
 void DeclContext::makeDeclVisibleInContext(NamedDecl *D) {
+  // FIXME: This feels like a hack. Should DeclarationName support
+  // template-ids, or is there a better way to keep specializations
+  // from being visible?
+  if (isa<ClassTemplateSpecializationDecl>(D))
+    return;
+
   DeclContext *PrimaryContext = getPrimaryContext();
   if (PrimaryContext != this) {
     PrimaryContext->makeDeclVisibleInContext(D);
@@ -486,6 +475,12 @@
   if (!D->getDeclName())
     return;
 
+  // FIXME: This feels like a hack. Should DeclarationName support
+  // template-ids, or is there a better way to keep specializations
+  // from being visible?
+  if (isa<ClassTemplateSpecializationDecl>(D))
+    return;
+
   bool MayBeRedeclaration = true;
 
   if (!isLookupMap()) {

Modified: cfe/trunk/lib/AST/DeclTemplate.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/DeclTemplate.cpp?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/lib/AST/DeclTemplate.cpp (original)
+++ cfe/trunk/lib/AST/DeclTemplate.cpp Tue Feb 17 17:15:12 2009
@@ -159,7 +159,7 @@
                   // class template specializations?
                   SpecializedTemplate->getIdentifier()),
     SpecializedTemplate(SpecializedTemplate),
-    NumTemplateArgs(NumTemplateArgs) {
+    NumTemplateArgs(NumTemplateArgs), SpecializationKind(TSK_Undeclared) {
   TemplateArgument *Arg = reinterpret_cast<TemplateArgument *>(this + 1);
   for (unsigned ArgIdx = 0; ArgIdx < NumTemplateArgs; ++ArgIdx, ++Arg)
     *Arg = TemplateArgs[ArgIdx];
@@ -170,12 +170,16 @@
                                         DeclContext *DC, SourceLocation L,
                                         ClassTemplateDecl *SpecializedTemplate,
                                         TemplateArgument *TemplateArgs, 
-                                        unsigned NumTemplateArgs) {
+                                        unsigned NumTemplateArgs,
+                                   ClassTemplateSpecializationDecl *PrevDecl) {
   unsigned Size = sizeof(ClassTemplateSpecializationDecl) + 
                   sizeof(TemplateArgument) * NumTemplateArgs;
   unsigned Align = llvm::AlignOf<ClassTemplateSpecializationDecl>::Alignment;
   void *Mem = Context.Allocate(Size, Align);
-  return new (Mem) ClassTemplateSpecializationDecl(DC, L, SpecializedTemplate,
-                                                   TemplateArgs, 
-                                                   NumTemplateArgs);
+  ClassTemplateSpecializationDecl *Result
+    = new (Mem) ClassTemplateSpecializationDecl(DC, L, SpecializedTemplate,
+                                                TemplateArgs, NumTemplateArgs);
+  // FIXME: Do we want a prettier type here?
+  Context.getTypeDeclType(Result, PrevDecl);
+  return Result;
 }

Modified: cfe/trunk/lib/Parse/ParseDecl.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Parse/ParseDecl.cpp?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/lib/Parse/ParseDecl.cpp (original)
+++ cfe/trunk/lib/Parse/ParseDecl.cpp Tue Feb 17 17:15:12 2009
@@ -228,7 +228,7 @@
   switch (Tok.getKind()) {
   case tok::kw_export:
   case tok::kw_template:
-    return ParseTemplateDeclaration(Context);
+    return ParseTemplateDeclarationOrSpecialization(Context);
   case tok::kw_namespace:
     return ParseNamespace(Context);
   case tok::kw_using:
@@ -2095,7 +2095,7 @@
       DS.AddAttributes(AttrList);
       AttrList = 0;  // Only apply the attributes to the first parameter.
     }
-    ParseDeclarationSpecifiers(DS);
+    ParseDeclarationSpecifiers(DS);    
 
     // Parse the declarator.  This is "PrototypeContext", because we must
     // accept either 'declarator' or 'abstract-declarator' here.

Modified: cfe/trunk/lib/Parse/ParseDeclCXX.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Parse/ParseDeclCXX.cpp?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/lib/Parse/ParseDeclCXX.cpp (original)
+++ cfe/trunk/lib/Parse/ParseDeclCXX.cpp Tue Feb 17 17:15:12 2009
@@ -310,17 +310,69 @@
   // Parse the (optional) nested-name-specifier.
   CXXScopeSpec SS;
   if (getLang().CPlusPlus && ParseOptionalCXXScopeSpecifier(SS)) {
+    // FIXME: can we get a class template specialization or
+    // template-id token here?
     if (Tok.isNot(tok::identifier))
       Diag(Tok, diag::err_expected_ident);
   }
 
-  // Parse the (optional) class name.
-  // FIXME: Alternatively, parse a simple-template-id.
+
+  // These variables encode the simple-template-id that we might end
+  // up parsing below. We don't translate this into a type
+  // automatically because (1) we want to create a separate
+  // declaration for each specialization, and (2) we want to retain
+  // more information about source locations that types provide.
+  DeclTy *Template = 0;
+  SourceLocation LAngleLoc, RAngleLoc;
+  TemplateArgList TemplateArgs;
+  TemplateArgIsTypeList TemplateArgIsType;
+  TemplateArgLocationList TemplateArgLocations;
+  ASTTemplateArgsPtr TemplateArgsPtr(Actions, 0, 0, 0);
+  
+
+  // Parse the (optional) class name or simple-template-id.
   IdentifierInfo *Name = 0;
   SourceLocation NameLoc;
   if (Tok.is(tok::identifier)) {
     Name = Tok.getIdentifierInfo();
     NameLoc = ConsumeToken();
+
+    if (Tok.is(tok::less)) {
+      // This is a simple-template-id.
+      Action::TemplateNameKind TNK 
+        = Actions.isTemplateName(*Name, CurScope, Template, &SS);
+
+      bool Invalid = false;
+
+      // Parse the enclosed template argument list.
+      if (TNK != Action::TNK_Non_template)
+        Invalid = ParseTemplateIdAfterTemplateName(Template, NameLoc,
+                                                   &SS, true, LAngleLoc, 
+                                                   TemplateArgs,
+                                                   TemplateArgIsType,
+                                                   TemplateArgLocations,
+                                                   RAngleLoc);
+      
+      TemplateArgsPtr.reset(&TemplateArgs[0], &TemplateArgIsType[0],
+                            TemplateArgs.size());
+
+      if (TNK != Action::TNK_Class_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 ';'.
+        SourceRange Range(NameLoc);
+        if (SS.isNotEmpty())
+          Range.setBegin(SS.getBeginLoc());
+        else if (!Invalid)
+          
+        Diag(LAngleLoc, diag::err_template_spec_syntax_non_template)
+          << Name << static_cast<int>(TNK) << Range;
+
+        DS.SetTypeSpecError();
+        SkipUntil(tok::semi, false, true);
+        return;
+      }
+    }
   }
 
   // There are three options here.  If we have 'struct foo;', then
@@ -347,7 +399,23 @@
 
   // Create the tag portion of the class or class template.
   DeclTy *TagOrTempDecl;
-  if (TemplateParams && TK != Action::TK_Reference)
+  if (Template && TK != Action::TK_Reference)
+    // Explicit specialization or class template partial
+    // specialization. Let semantic analysis decide.
+
+    // FIXME: we want a source range covering the simple-template-id.
+    TagOrTempDecl 
+      = Actions.ActOnClassTemplateSpecialization(CurScope, TagType, TK,
+                                                 StartLoc, SS, /*Range*/
+                                                 Template, NameLoc, 
+                                                 LAngleLoc, TemplateArgsPtr,
+                                                 &TemplateArgLocations[0],
+                                                 RAngleLoc, Attr,
+                       Action::MultiTemplateParamsArg(Actions, 
+                                    TemplateParams? &(*TemplateParams)[0] : 0,
+                                 TemplateParams? TemplateParams->size() : 0));
+
+  else if (TemplateParams && TK != Action::TK_Reference)
     TagOrTempDecl = Actions.ActOnClassTemplate(CurScope, TagType, TK, StartLoc,
                                                SS, Name, NameLoc, Attr,
                        Action::MultiTemplateParamsArg(Actions, 

Modified: cfe/trunk/lib/Parse/ParseTemplate.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Parse/ParseTemplate.cpp?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/lib/Parse/ParseTemplate.cpp (original)
+++ cfe/trunk/lib/Parse/ParseTemplate.cpp Tue Feb 17 17:15:12 2009
@@ -19,12 +19,23 @@
 
 using namespace clang;
 
-/// ParseTemplateDeclaration - Parse a template declaration, which includes 
-/// the template parameter list and either a function of class declaration.
+/// \brief Parse a template declaration or an explicit specialization.
+///
+/// Template declarations include one or more template parameter lists
+/// and either the function or class template declaration. Explicit
+/// specializations contain one or more 'template < >' prefixes
+/// followed by a (possibly templated) declaration. Since the
+/// syntactic form of both features is nearly identical, we parse all
+/// of the template headers together and let semantic analysis sort
+/// the declarations from the explicit specializations.
 ///
 ///       template-declaration: [C++ temp]
 ///         'export'[opt] 'template' '<' template-parameter-list '>' declaration
-Parser::DeclTy *Parser::ParseTemplateDeclaration(unsigned Context) {
+///
+///       explicit-specialization: [ C++ temp.expl.spec]
+///         'template' '<' '>' declaration
+Parser::DeclTy *
+Parser::ParseTemplateDeclarationOrSpecialization(unsigned Context) {
   assert((Tok.is(tok::kw_export) || Tok.is(tok::kw_template)) && 
 	 "Token does not start a template declaration.");
   
@@ -40,7 +51,7 @@
   //
   // We parse multiple levels non-recursively so that we can build a
   // single data structure containing all of the template parameter
-  // lists easily differentiate between the case above and:
+  // lists to easily differentiate between the case above and:
   //
   //   template<typename T>
   //   class A {
@@ -370,6 +381,68 @@
   return Param;
 }
 
+/// \brief Parses a template-id that after the template name has
+/// already been parsed.
+///
+/// This routine takes care of parsing the enclosed template argument
+/// list ('<' template-parameter-list [opt] '>') and placing the
+/// results into a form that can be transferred to semantic analysis.
+///
+/// \param Template the template declaration produced by isTemplateName
+///
+/// \param TemplateNameLoc the source location of the template name
+///
+/// \param SS if non-NULL, the nested-name-specifier preceding the
+/// template name.
+///
+/// \param ConsumeLastToken if true, then we will consume the last
+/// token that forms the template-id. Otherwise, we will leave the
+/// last token in the stream (e.g., so that it can be replaced with an
+/// annotation token).
+bool 
+Parser::ParseTemplateIdAfterTemplateName(DeclTy *Template,
+                                         SourceLocation TemplateNameLoc, 
+                                         const CXXScopeSpec *SS,
+                                         bool ConsumeLastToken,
+                                         SourceLocation &LAngleLoc,
+                                         TemplateArgList &TemplateArgs,
+                                    TemplateArgIsTypeList &TemplateArgIsType,
+                               TemplateArgLocationList &TemplateArgLocations,
+                                         SourceLocation &RAngleLoc) {
+  assert(Tok.is(tok::less) && "Must have already parsed the template-name");
+
+  // Consume the '<'.
+  LAngleLoc = ConsumeToken();
+
+  // Parse the optional template-argument-list.
+  bool Invalid = false;
+  {
+    GreaterThanIsOperatorScope G(GreaterThanIsOperator, false);
+    if (Tok.isNot(tok::greater))
+      Invalid = ParseTemplateArgumentList(TemplateArgs, TemplateArgIsType,
+                                          TemplateArgLocations);
+
+    if (Invalid) {
+      // Try to find the closing '>'.
+      SkipUntil(tok::greater, true, !ConsumeLastToken);
+
+      return true;
+    }
+  }
+
+  if (Tok.isNot(tok::greater))
+    return true;
+
+  // Determine the location of the '>'. Only consume this token if the
+  // caller asked us to.
+  RAngleLoc = Tok.getLocation();
+
+  if (ConsumeLastToken)
+    ConsumeToken();
+
+  return false;
+}
+                                              
 /// AnnotateTemplateIdToken - The current token is an identifier that
 /// refers to the template declaration Template, and is followed by a
 /// '<'. Turn this template-id into a template-id annotation token.
@@ -382,46 +455,44 @@
   // Consume the template-name.
   SourceLocation TemplateNameLoc = ConsumeToken();
 
-  // Consume the '<'.
-  SourceLocation LAngleLoc = ConsumeToken();
-
-  // Parse the optional template-argument-list.
+  // Parse the enclosed template argument list.
+  SourceLocation LAngleLoc, RAngleLoc;
   TemplateArgList TemplateArgs;
   TemplateArgIsTypeList TemplateArgIsType;
   TemplateArgLocationList TemplateArgLocations;
+  bool Invalid = ParseTemplateIdAfterTemplateName(Template, TemplateNameLoc,
+                                                  SS, false, LAngleLoc, 
+                                                  TemplateArgs, 
+                                                  TemplateArgIsType,
+                                                  TemplateArgLocations,
+                                                  RAngleLoc);
+
+  ASTTemplateArgsPtr TemplateArgsPtr(Actions, &TemplateArgs[0],
+                                     &TemplateArgIsType[0],
+                                     TemplateArgs.size());
 
-  {
-    GreaterThanIsOperatorScope G(GreaterThanIsOperator, false);
-    if (Tok.isNot(tok::greater) && 
-        ParseTemplateArgumentList(TemplateArgs, TemplateArgIsType,
-                                  TemplateArgLocations)) {
-      // Try to find the closing '>'.
-      SkipUntil(tok::greater, true, true);
+  if (Invalid) // FIXME: How to recover from a broken template-id?
+    return; 
 
-      // Clean up any template arguments that we successfully parsed.
-      ASTTemplateArgsPtr TemplateArgsPtr(Actions, &TemplateArgs[0],
-                                         &TemplateArgIsType[0],
-                                         TemplateArgs.size());
-      
+  // Build the annotation token.
+  if (TNK == Action::TNK_Class_template) {
+    Action::TypeResult Type 
+      = Actions.ActOnClassTemplateId(Template, TemplateNameLoc,
+                                     LAngleLoc, TemplateArgsPtr,
+                                     &TemplateArgLocations[0],
+                                     RAngleLoc, SS);
+    if (Type.isInvalid()) // FIXME: better recovery?
       return;
-    }
-  }
-
-  if (Tok.isNot(tok::greater))
-    return;
 
-  // Determine the location of the '>'. We won't actually consume this
-  // token, because we'll be replacing it with the template-id.
-  SourceLocation RAngleLoc = Tok.getLocation();
-  
-  // Build the annotation token.
-  if (TNK == Action::TNK_Function_template) {
+    Tok.setKind(tok::annot_typename);
+    Tok.setAnnotationValue(Type.get());
+  } else {
     // This is a function template. We'll be building a template-id
     // annotation token.
     Tok.setKind(tok::annot_template_id);    
     TemplateIdAnnotation *TemplateId 
       = (TemplateIdAnnotation *)malloc(sizeof(TemplateIdAnnotation) + 
-                                  sizeof(void*) * TemplateArgs.size());
+                                       sizeof(void*) * TemplateArgs.size());
     TemplateId->TemplateNameLoc = TemplateNameLoc;
     TemplateId->Template = Template;
     TemplateId->LAngleLoc = LAngleLoc;
@@ -430,24 +501,6 @@
     for (unsigned Arg = 0, ArgEnd = TemplateArgs.size(); Arg != ArgEnd; ++Arg)
       Args[Arg] = TemplateArgs[Arg];
     Tok.setAnnotationValue(TemplateId);
-  } else {
-    // This is a type template, e.g., a class template, template
-    // template parameter, or template alias. We'll be building a
-    // "typename" annotation token.
-    ASTTemplateArgsPtr TemplateArgsPtr(Actions, &TemplateArgs[0],
-                                       &TemplateArgIsType[0],
-                                       TemplateArgs.size());
-    TypeTy *Ty 
-      = Actions.ActOnClassTemplateSpecialization(Template, TemplateNameLoc,
-                                                 LAngleLoc, TemplateArgsPtr,
-                                                 &TemplateArgLocations[0],
-                                                 RAngleLoc, SS);
-
-    if (!Ty) // Something went wrong; don't annotate
-      return;
-
-    Tok.setKind(tok::annot_typename);
-    Tok.setAnnotationValue(Ty);
   }
 
   // Common fields for the annotation token

Modified: cfe/trunk/lib/Sema/Sema.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/Sema.h?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/lib/Sema/Sema.h (original)
+++ cfe/trunk/lib/Sema/Sema.h Tue Feb 17 17:15:12 2009
@@ -1513,14 +1513,26 @@
                      AttributeList *Attr,
                      MultiTemplateParamsArg TemplateParameterLists);
 
-  virtual TypeTy * 
-  ActOnClassTemplateSpecialization(DeclTy *Template, 
-                                   SourceLocation TemplateLoc,
+  virtual TypeResult
+  ActOnClassTemplateId(DeclTy *Template, SourceLocation TemplateLoc,
+                       SourceLocation LAngleLoc,
+                       ASTTemplateArgsPtr TemplateArgs,
+                       SourceLocation *TemplateArgLocs,
+                       SourceLocation RAngleLoc,
+                       const CXXScopeSpec *SS);
+  
+  virtual DeclTy *
+  ActOnClassTemplateSpecialization(Scope *S, unsigned TagSpec, TagKind TK,
+                                   SourceLocation KWLoc, 
+                                   const CXXScopeSpec &SS,
+                                   DeclTy *Template,
+                                   SourceLocation TemplateNameLoc,
                                    SourceLocation LAngleLoc,
                                    ASTTemplateArgsPtr TemplateArgs,
                                    SourceLocation *TemplateArgLocs,
                                    SourceLocation RAngleLoc,
-                                   const CXXScopeSpec *SS = 0);
+                                   AttributeList *Attr,
+                                 MultiTemplateParamsArg TemplateParameterLists);
 
   bool CheckTemplateArgumentList(TemplateDecl *Template,
                                  SourceLocation TemplateLoc,

Modified: cfe/trunk/lib/Sema/SemaTemplate.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaTemplate.cpp?rev=64848&r1=64847&r2=64848&view=diff

==============================================================================
--- cfe/trunk/lib/Sema/SemaTemplate.cpp (original)
+++ cfe/trunk/lib/Sema/SemaTemplate.cpp Tue Feb 17 17:15:12 2009
@@ -645,14 +645,13 @@
   return Invalid;
 }
 
-Action::TypeTy * 
-Sema::ActOnClassTemplateSpecialization(DeclTy *TemplateD,
-                                       SourceLocation TemplateLoc,
-                                       SourceLocation LAngleLoc,
-                                       ASTTemplateArgsPtr TemplateArgs,
-                                       SourceLocation *TemplateArgLocs,
-                                       SourceLocation RAngleLoc,
-                                       const CXXScopeSpec *SS) {
+Action::TypeResult
+Sema::ActOnClassTemplateId(DeclTy *TemplateD, SourceLocation TemplateLoc,
+                           SourceLocation LAngleLoc, 
+                           ASTTemplateArgsPtr TemplateArgs,
+                           SourceLocation *TemplateArgLocs,
+                           SourceLocation RAngleLoc,
+                           const CXXScopeSpec *SS) {
   TemplateDecl *Template = cast<TemplateDecl>(static_cast<Decl *>(TemplateD));
   ClassTemplateDecl *ClassTemplate = cast<ClassTemplateDecl>(Template);
 
@@ -662,7 +661,7 @@
   if (CheckTemplateArgumentList(Template, TemplateLoc, LAngleLoc, 
                                 TemplateArgs, TemplateArgLocs, RAngleLoc,
                                 ConvertedTemplateArgs))
-    return 0;
+    return true;
 
   assert((ConvertedTemplateArgs.size() == 
             Template->getTemplateParameters()->size()) &&
@@ -678,15 +677,15 @@
     = ClassTemplate->getSpecializations().FindNodeOrInsertPos(ID, InsertPos);
   if (!Decl) {
     // This is the first time we have referenced this class template
-    // specialization. Create an appropriate declaration node and add
-    // it to the list of specializations. This is the canonical
-    // declaration of the class template.
+    // specialization. Create the canonical declaration and add it to
+    // the set of specializations.
     Decl = ClassTemplateSpecializationDecl::Create(Context, 
                                            ClassTemplate->getDeclContext(),
                                                    TemplateLoc,
                                                    ClassTemplate,
                                                    &ConvertedTemplateArgs[0],
-                                           ConvertedTemplateArgs.size());
+                                                ConvertedTemplateArgs.size(),
+                                                   0);
     ClassTemplate->getSpecializations().InsertNode(Decl, InsertPos);
   }
 
@@ -1517,3 +1516,136 @@
   return Diag(TemplateLoc, diag::err_template_outside_namespace_or_class_scope)
     << TemplateRange;
 }
+
+Sema::DeclTy *
+Sema::ActOnClassTemplateSpecialization(Scope *S, unsigned TagSpec, TagKind TK,
+                                       SourceLocation KWLoc, 
+                                       const CXXScopeSpec &SS,
+                                       DeclTy *TemplateD,
+                                       SourceLocation TemplateNameLoc,
+                                       SourceLocation LAngleLoc,
+                                       ASTTemplateArgsPtr TemplateArgs,
+                                       SourceLocation *TemplateArgLocs,
+                                       SourceLocation RAngleLoc,
+                                       AttributeList *Attr,
+                               MultiTemplateParamsArg TemplateParameterLists) {
+  // FIXME: We need to match up the scope-specifier with the template
+  // parameter lists, and will eventually end up with a single
+  // template parameter list remaining (which applies to
+  // TemplateIdType).
+  assert(TemplateParameterLists.size() == 1 && 
+         "Clang doesn't handle with ill-formed specializations yet.");
+
+  TemplateParameterList *TemplateParams = 
+    static_cast<TemplateParameterList*>(*TemplateParameterLists.get());
+  assert(TemplateParams->size() == 0 &&
+         "Clang doesn't handle class template partial specializations yet");
+
+  // Find the class template we're specializing
+  ClassTemplateDecl *ClassTemplate 
+    = dyn_cast_or_null<ClassTemplateDecl>(static_cast<Decl *>(TemplateD));
+  if (!ClassTemplate)
+    return 0;
+
+  // Check that the specialization uses the same tag kind as the
+  // original template.
+  TagDecl::TagKind Kind;
+  switch (TagSpec) {
+  default: assert(0 && "Unknown tag type!");
+  case DeclSpec::TST_struct: Kind = TagDecl::TK_struct; break;
+  case DeclSpec::TST_union:  Kind = TagDecl::TK_union; break;
+  case DeclSpec::TST_class:  Kind = TagDecl::TK_class; break;
+  }
+  if (ClassTemplate->getTemplatedDecl()->getTagKind() != Kind) {
+    Diag(KWLoc, diag::err_use_with_wrong_tag) << ClassTemplate;
+    Diag(ClassTemplate->getTemplatedDecl()->getLocation(), 
+         diag::note_previous_use);
+    Kind = ClassTemplate->getTemplatedDecl()->getTagKind();
+  }
+
+  // Check that the template argument list is well-formed for this
+  // template.
+  llvm::SmallVector<TemplateArgument, 16> ConvertedTemplateArgs;
+  if (CheckTemplateArgumentList(ClassTemplate, TemplateNameLoc, LAngleLoc, 
+                                TemplateArgs, TemplateArgLocs, RAngleLoc,
+                                ConvertedTemplateArgs))
+    return 0;
+
+  assert((ConvertedTemplateArgs.size() == 
+            ClassTemplate->getTemplateParameters()->size()) &&
+         "Converted template argument list is too short!");
+  
+  // Find the class template specialization declaration that
+  // corresponds to these arguments.
+  llvm::FoldingSetNodeID ID;
+  ClassTemplateSpecializationDecl::Profile(ID, &ConvertedTemplateArgs[0],
+                                           ConvertedTemplateArgs.size());
+  void *InsertPos = 0;
+  ClassTemplateSpecializationDecl *PrevDecl
+    = ClassTemplate->getSpecializations().FindNodeOrInsertPos(ID, InsertPos);
+
+  ClassTemplateSpecializationDecl *Specialization = 0;
+
+  if (PrevDecl && PrevDecl->getSpecializationKind() == TSK_Undeclared) {
+    // Since the only prior class template specialization with these
+    // arguments was referenced but not declared, reuse that
+    // declaration node as our own, updating its source location to
+    // reflect our new declaration.
+    // FIXME: update source locations
+    Specialization = PrevDecl;
+    PrevDecl = 0;
+  } else {
+    // Create a new class template specialization declaration node for
+    // this explicit specialization.
+    Specialization
+      = ClassTemplateSpecializationDecl::Create(Context, 
+                                             ClassTemplate->getDeclContext(),
+                                                TemplateNameLoc,
+                                                ClassTemplate,
+                                                &ConvertedTemplateArgs[0],
+                                                ConvertedTemplateArgs.size(),
+                                                PrevDecl);
+
+    if (PrevDecl) {
+      ClassTemplate->getSpecializations().RemoveNode(PrevDecl);
+      ClassTemplate->getSpecializations().GetOrInsertNode(Specialization);
+    } else {
+      ClassTemplate->getSpecializations().InsertNode(Specialization, 
+                                                     InsertPos);
+    }
+  }
+
+  // Note that this is an explicit specialization.
+  Specialization->setSpecializationKind(TSK_ExplicitSpecialization);
+
+  // Check that this isn't a redefinition of this specialization.
+  if (TK == TK_Definition) {
+    if (RecordDecl *Def = Specialization->getDefinition(Context)) {
+      // FIXME: Should also handle explicit specialization after 
+      // implicit instantiation with a special diagnostic.
+      SourceRange Range(TemplateNameLoc, RAngleLoc);
+      Diag(TemplateNameLoc, diag::err_redefinition) 
+        << Specialization << Range;
+      Diag(Def->getLocation(), diag::note_previous_definition);
+      Specialization->setInvalidDecl();
+      return 0;
+    }
+  }
+
+  // FIXME: Do we want to create a nicely sugared type to use as the
+  // type for this explicit specialization?
+
+  // Set the lexical context. If the tag has a C++ scope specifier,
+  // the lexical context will be different from the semantic context.
+  Specialization->setLexicalDeclContext(CurContext);
+  
+  // We may be starting the definition of this specialization.
+  if (TK == TK_Definition)
+    Specialization->startDefinition();
+
+  // Add the specialization into its lexical context, so that it can
+  // be seen when iterating through the list of declarations in that
+  // context. However, specializations are not found by name lookup.
+  CurContext->addDecl(Specialization);
+  return Specialization;
+}

Added: cfe/trunk/test/SemaTemplate/class-template-spec.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaTemplate/class-template-spec.cpp?rev=64848&view=auto

==============================================================================
--- cfe/trunk/test/SemaTemplate/class-template-spec.cpp (added)
+++ cfe/trunk/test/SemaTemplate/class-template-spec.cpp Tue Feb 17 17:15:12 2009
@@ -0,0 +1,31 @@
+// RUN: clang -fsyntax-only -verify %s
+template<typename T, typename U = int> class A;
+
+template<> class A<double, double>; // expected-note{{forward declaration}}
+
+template<> class A<float, float> {  // expected-note{{previous definition}}
+  int x;
+};
+
+template<> class A<float> { // expected-note{{previous definition}}
+  int y;
+};
+
+int test_specs(A<float, float> *a1, A<float, int> *a2) {
+  return a1->x + a2->y;
+}
+
+int test_incomplete_specs(A<double, double> *a1, 
+                          A<double> *a2) // FIXME: expected-note{{forward declaration}}
+{
+  (void)a1->x; // expected-error{{incomplete definition of type 'A<double, double>'}}
+  (void)a2->x; // expected-error{{incomplete definition of type 'A<double>'}}
+}
+
+typedef float FLOAT;
+
+template<> class A<float, FLOAT>;
+
+template<> class A<FLOAT, float> { }; // expected-error{{redefinition}}
+
+template<> class A<float, int> { }; // expected-error{{redefinition}}





More information about the cfe-commits mailing list