mirror of
https://github.com/intel/llvm.git
synced 2026-02-07 16:11:27 +08:00
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.
llvm-svn: 64848
This commit is contained in:
@@ -202,7 +202,7 @@ public:
|
||||
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 @@ public:
|
||||
case FunctionTemplate:
|
||||
case ClassTemplate:
|
||||
case TemplateTemplateParm:
|
||||
return IdentifierNamespace(IDNS_Tag | IDNS_Ordinary);
|
||||
return IDNS_Tag | IDNS_Ordinary;
|
||||
|
||||
case ClassTemplateSpecialization:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ DECL_RANGE(Named, OverloadedFunction, ObjCCompatibleAlias)
|
||||
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)
|
||||
|
||||
@@ -521,6 +521,23 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// \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 @@ class ClassTemplateSpecializationDecl
|
||||
|
||||
/// \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 @@ public:
|
||||
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 @@ public:
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -274,6 +274,8 @@ DIAG(err_expected_type_id_after, ERROR,
|
||||
"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
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ public:
|
||||
/// 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 @@ public:
|
||||
/// \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.
|
||||
///
|
||||
/// \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,
|
||||
/// 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
|
||||
///
|
||||
/// \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 -------------------------===//
|
||||
|
||||
|
||||
@@ -995,7 +995,9 @@ public:
|
||||
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; }
|
||||
|
||||
@@ -571,6 +571,20 @@ namespace clang
|
||||
#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 @@ namespace clang
|
||||
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;
|
||||
|
||||
@@ -996,7 +996,7 @@ private:
|
||||
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 @@ private:
|
||||
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,
|
||||
|
||||
@@ -319,17 +319,6 @@ DeclContext *DeclContext::getPrimaryContext() {
|
||||
// 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 @@ DeclContext *DeclContext::getPrimaryContext() {
|
||||
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::getPrimaryContext() {
|
||||
|
||||
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 @@ const DeclContext *DeclContext::getLookupContext() const {
|
||||
}
|
||||
|
||||
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 @@ void DeclContext::makeDeclVisibleInContextImpl(NamedDecl *D) {
|
||||
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()) {
|
||||
|
||||
@@ -159,7 +159,7 @@ ClassTemplateSpecializationDecl(DeclContext *DC, SourceLocation L,
|
||||
// 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 @@ ClassTemplateSpecializationDecl::Create(ASTContext &Context,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ Parser::DeclTy *Parser::ParseDeclaration(unsigned Context) {
|
||||
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 @@ void Parser::ParseFunctionDeclarator(SourceLocation LParenLoc, Declarator &D,
|
||||
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.
|
||||
|
||||
@@ -310,17 +310,69 @@ void Parser::ParseClassSpecifier(DeclSpec &DS,
|
||||
// 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 @@ void Parser::ParseClassSpecifier(DeclSpec &DS,
|
||||
|
||||
// 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,
|
||||
|
||||
@@ -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 @@ Parser::DeclTy *Parser::ParseTemplateDeclaration(unsigned Context) {
|
||||
//
|
||||
// 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 @@ Parser::ParseNonTypeTemplateParameter(unsigned Depth, unsigned Position) {
|
||||
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 @@ void Parser::AnnotateTemplateIdToken(DeclTy *Template, TemplateNameKind TNK,
|
||||
// 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);
|
||||
|
||||
{
|
||||
GreaterThanIsOperatorScope G(GreaterThanIsOperator, false);
|
||||
if (Tok.isNot(tok::greater) &&
|
||||
ParseTemplateArgumentList(TemplateArgs, TemplateArgIsType,
|
||||
TemplateArgLocations)) {
|
||||
// Try to find the closing '>'.
|
||||
SkipUntil(tok::greater, true, true);
|
||||
ASTTemplateArgsPtr TemplateArgsPtr(Actions, &TemplateArgs[0],
|
||||
&TemplateArgIsType[0],
|
||||
TemplateArgs.size());
|
||||
|
||||
// Clean up any template arguments that we successfully parsed.
|
||||
ASTTemplateArgsPtr TemplateArgsPtr(Actions, &TemplateArgs[0],
|
||||
&TemplateArgIsType[0],
|
||||
TemplateArgs.size());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Invalid) // FIXME: How to recover from a broken template-id?
|
||||
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) {
|
||||
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;
|
||||
|
||||
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 @@ void Parser::AnnotateTemplateIdToken(DeclTy *Template, TemplateNameKind TNK,
|
||||
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
|
||||
|
||||
@@ -1513,14 +1513,26 @@ public:
|
||||
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,
|
||||
|
||||
@@ -645,14 +645,13 @@ bool Sema::CheckTemplateParameterList(TemplateParameterList *NewParams,
|
||||
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 @@ Sema::ActOnClassTemplateSpecialization(DeclTy *TemplateD,
|
||||
if (CheckTemplateArgumentList(Template, TemplateLoc, LAngleLoc,
|
||||
TemplateArgs, TemplateArgLocs, RAngleLoc,
|
||||
ConvertedTemplateArgs))
|
||||
return 0;
|
||||
return true;
|
||||
|
||||
assert((ConvertedTemplateArgs.size() ==
|
||||
Template->getTemplateParameters()->size()) &&
|
||||
@@ -678,15 +677,15 @@ Sema::ActOnClassTemplateSpecialization(DeclTy *TemplateD,
|
||||
= 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 @@ Sema::CheckTemplateDeclScope(Scope *S,
|
||||
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;
|
||||
}
|
||||
|
||||
31
clang/test/SemaTemplate/class-template-spec.cpp
Normal file
31
clang/test/SemaTemplate/class-template-spec.cpp
Normal file
@@ -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}}
|
||||
Reference in New Issue
Block a user