Initial stab at implement dependent member references to member

templates, e.g.,
  
  x.template get<T>

We can now parse these, represent them within an UnresolvedMemberExpr
expression, then instantiate that expression node in simple cases.

This allows us to stumble through parsing LLVM's Casting.h.

llvm-svn: 81300
This commit is contained in:
Douglas Gregor 2009-09-09 00:23:06 +00:00
parent c466e31309
commit 308047d3a5
13 changed files with 362 additions and 75 deletions

View File

@ -1407,8 +1407,12 @@ class CXXUnresolvedMemberExpr : public Expr {
/// \brief Whether this member expression used the '->' operator or
/// the '.' operator.
bool IsArrow;
bool IsArrow : 1;
/// \brief Whether this member expression has explicitly-specified template
/// arguments.
bool HasExplicitTemplateArgumentList : 1;
/// \brief The location of the '->' or '.' operator.
SourceLocation OperatorLoc;
@ -1435,6 +1439,36 @@ class CXXUnresolvedMemberExpr : public Expr {
/// \brief The location of the member name.
SourceLocation MemberLoc;
/// \brief Retrieve the explicit template argument list that followed the
/// member template name, if any.
ExplicitTemplateArgumentList *getExplicitTemplateArgumentList() {
if (!HasExplicitTemplateArgumentList)
return 0;
return reinterpret_cast<ExplicitTemplateArgumentList *>(this + 1);
}
/// \brief Retrieve the explicit template argument list that followed the
/// member template name, if any.
const ExplicitTemplateArgumentList *getExplicitTemplateArgumentList() const {
return const_cast<CXXUnresolvedMemberExpr *>(this)
->getExplicitTemplateArgumentList();
}
CXXUnresolvedMemberExpr(ASTContext &C,
Expr *Base, bool IsArrow,
SourceLocation OperatorLoc,
NestedNameSpecifier *Qualifier,
SourceRange QualifierRange,
NamedDecl *FirstQualifierFoundInScope,
DeclarationName Member,
SourceLocation MemberLoc,
bool HasExplicitTemplateArgs,
SourceLocation LAngleLoc,
const TemplateArgument *TemplateArgs,
unsigned NumTemplateArgs,
SourceLocation RAngleLoc);
public:
CXXUnresolvedMemberExpr(ASTContext &C,
Expr *Base, bool IsArrow,
@ -1444,12 +1478,28 @@ public:
NamedDecl *FirstQualifierFoundInScope,
DeclarationName Member,
SourceLocation MemberLoc)
: Expr(CXXUnresolvedMemberExprClass, C.DependentTy, true, true),
Base(Base), IsArrow(IsArrow), OperatorLoc(OperatorLoc),
Qualifier(Qualifier), QualifierRange(QualifierRange),
FirstQualifierFoundInScope(FirstQualifierFoundInScope),
Member(Member), MemberLoc(MemberLoc) { }
: Expr(CXXUnresolvedMemberExprClass, C.DependentTy, true, true),
Base(Base), IsArrow(IsArrow), HasExplicitTemplateArgumentList(false),
OperatorLoc(OperatorLoc),
Qualifier(Qualifier), QualifierRange(QualifierRange),
FirstQualifierFoundInScope(FirstQualifierFoundInScope),
Member(Member), MemberLoc(MemberLoc) { }
static CXXUnresolvedMemberExpr *
Create(ASTContext &C,
Expr *Base, bool IsArrow,
SourceLocation OperatorLoc,
NestedNameSpecifier *Qualifier,
SourceRange QualifierRange,
NamedDecl *FirstQualifierFoundInScope,
DeclarationName Member,
SourceLocation MemberLoc,
bool HasExplicitTemplateArgs,
SourceLocation LAngleLoc,
const TemplateArgument *TemplateArgs,
unsigned NumTemplateArgs,
SourceLocation RAngleLoc);
/// \brief Retrieve the base object of this member expressions,
/// e.g., the \c x in \c x.m.
Expr *getBase() { return cast<Expr>(Base); }
@ -1497,10 +1547,57 @@ public:
SourceLocation getMemberLoc() const { return MemberLoc; }
void setMemberLoc(SourceLocation L) { MemberLoc = L; }
/// \brief Determines whether this member expression actually had a C++
/// template argument list explicitly specified, e.g., x.f<int>.
bool hasExplicitTemplateArgumentList() {
return HasExplicitTemplateArgumentList;
}
/// \brief Retrieve the location of the left angle bracket following the
/// member name ('<'), if any.
SourceLocation getLAngleLoc() const {
if (!HasExplicitTemplateArgumentList)
return SourceLocation();
return getExplicitTemplateArgumentList()->LAngleLoc;
}
/// \brief Retrieve the template arguments provided as part of this
/// template-id.
const TemplateArgument *getTemplateArgs() const {
if (!HasExplicitTemplateArgumentList)
return 0;
return getExplicitTemplateArgumentList()->getTemplateArgs();
}
/// \brief Retrieve the number of template arguments provided as part of this
/// template-id.
unsigned getNumTemplateArgs() const {
if (!HasExplicitTemplateArgumentList)
return 0;
return getExplicitTemplateArgumentList()->NumTemplateArgs;
}
/// \brief Retrieve the location of the right angle bracket following the
/// template arguments ('>').
SourceLocation getRAngleLoc() const {
if (!HasExplicitTemplateArgumentList)
return SourceLocation();
return getExplicitTemplateArgumentList()->RAngleLoc;
}
virtual SourceRange getSourceRange() const {
if (HasExplicitTemplateArgumentList)
return SourceRange(Base->getSourceRange().getBegin(),
getRAngleLoc());
return SourceRange(Base->getSourceRange().getBegin(),
MemberLoc);
}
static bool classof(const Stmt *T) {
return T->getStmtClass() == CXXUnresolvedMemberExprClass;
}

View File

@ -217,7 +217,7 @@ public:
/// resolved prior to template instantiation.
///
/// This kind of template name refers to a dependent template name,
/// including its nested name specifier. For example,
/// including its nested name specifier (if any). For example,
/// DependentTemplateName can refer to "MetaFun::template apply",
/// where "MetaFun::" is the nested name specifier and "apply" is the
/// template name referenced. The "template" keyword is implied.

View File

@ -3319,7 +3319,8 @@ TemplateName ASTContext::getQualifiedTemplateName(NestedNameSpecifier *NNS,
/// template name such as \c MetaFun::template apply.
TemplateName ASTContext::getDependentTemplateName(NestedNameSpecifier *NNS,
const IdentifierInfo *Name) {
assert(NNS->isDependent() && "Nested name specifier must be dependent");
assert((!NNS || NNS->isDependent()) &&
"Nested name specifier must be dependent");
llvm::FoldingSetNodeID ID;
DependentTemplateName::Profile(ID, NNS, Name);

View File

@ -523,6 +523,77 @@ Stmt::child_iterator CXXUnresolvedConstructExpr::child_end() {
return child_iterator(reinterpret_cast<Stmt **>(this + 1) + NumArgs);
}
CXXUnresolvedMemberExpr::CXXUnresolvedMemberExpr(ASTContext &C,
Expr *Base, bool IsArrow,
SourceLocation OperatorLoc,
NestedNameSpecifier *Qualifier,
SourceRange QualifierRange,
NamedDecl *FirstQualifierFoundInScope,
DeclarationName Member,
SourceLocation MemberLoc,
bool HasExplicitTemplateArgs,
SourceLocation LAngleLoc,
const TemplateArgument *TemplateArgs,
unsigned NumTemplateArgs,
SourceLocation RAngleLoc)
: Expr(CXXUnresolvedMemberExprClass, C.DependentTy, true, true),
Base(Base), IsArrow(IsArrow),
HasExplicitTemplateArgumentList(HasExplicitTemplateArgs),
OperatorLoc(OperatorLoc),
Qualifier(Qualifier), QualifierRange(QualifierRange),
FirstQualifierFoundInScope(FirstQualifierFoundInScope),
Member(Member), MemberLoc(MemberLoc)
{
if (HasExplicitTemplateArgumentList) {
ExplicitTemplateArgumentList *ETemplateArgs
= getExplicitTemplateArgumentList();
ETemplateArgs->LAngleLoc = LAngleLoc;
ETemplateArgs->RAngleLoc = RAngleLoc;
ETemplateArgs->NumTemplateArgs = NumTemplateArgs;
TemplateArgument *SavedTemplateArgs = ETemplateArgs->getTemplateArgs();
for (unsigned I = 0; I < NumTemplateArgs; ++I)
new (SavedTemplateArgs + I) TemplateArgument(TemplateArgs[I]);
}
}
CXXUnresolvedMemberExpr *
CXXUnresolvedMemberExpr::Create(ASTContext &C,
Expr *Base, bool IsArrow,
SourceLocation OperatorLoc,
NestedNameSpecifier *Qualifier,
SourceRange QualifierRange,
NamedDecl *FirstQualifierFoundInScope,
DeclarationName Member,
SourceLocation MemberLoc,
bool HasExplicitTemplateArgs,
SourceLocation LAngleLoc,
const TemplateArgument *TemplateArgs,
unsigned NumTemplateArgs,
SourceLocation RAngleLoc)
{
if (!HasExplicitTemplateArgs)
return new (C) CXXUnresolvedMemberExpr(C, Base, IsArrow, OperatorLoc,
Qualifier, QualifierRange,
FirstQualifierFoundInScope,
Member, MemberLoc);
void *Mem = C.Allocate(sizeof(CXXUnresolvedMemberExpr) +
sizeof(ExplicitTemplateArgumentList) +
sizeof(TemplateArgument) * NumTemplateArgs,
llvm::alignof<CXXUnresolvedMemberExpr>());
return new (Mem) CXXUnresolvedMemberExpr(C, Base, IsArrow, OperatorLoc,
Qualifier, QualifierRange,
FirstQualifierFoundInScope,
Member,
MemberLoc,
HasExplicitTemplateArgs,
LAngleLoc,
TemplateArgs,
NumTemplateArgs,
RAngleLoc);
}
Stmt::child_iterator CXXUnresolvedMemberExpr::child_begin() {
return child_iterator(&Base);
}

View File

@ -42,7 +42,7 @@ NestedNameSpecifier *
NestedNameSpecifier::Create(ASTContext &Context, NestedNameSpecifier *Prefix,
IdentifierInfo *II) {
assert(II && "Identifier cannot be NULL");
assert(Prefix && Prefix->isDependent() && "Prefix must be dependent");
assert((!Prefix || Prefix->isDependent()) && "Prefix must be dependent");
NestedNameSpecifier Mockup;
Mockup.Prefix.setPointer(Prefix);

View File

@ -493,12 +493,10 @@ void StmtPrinter::VisitTemplateIdRefExpr(TemplateIdRefExpr *Node) {
if (Node->getQualifier())
Node->getQualifier()->print(OS, Policy);
Node->getTemplateName().print(OS, Policy, true);
OS << '<';
OS << TemplateSpecializationType::PrintTemplateArgumentList(
Node->getTemplateArgs(),
Node->getNumTemplateArgs(),
Policy);
OS << '>';
}
void StmtPrinter::VisitObjCIvarRefExpr(ObjCIvarRefExpr *Node) {
@ -1154,7 +1152,18 @@ void StmtPrinter::VisitCXXUnresolvedMemberExpr(CXXUnresolvedMemberExpr *Node) {
OS << (Node->isArrow() ? "->" : ".");
if (NestedNameSpecifier *Qualifier = Node->getQualifier())
Qualifier->print(OS, Policy);
else if (Node->hasExplicitTemplateArgumentList())
// FIXME: Track use of "template" keyword explicitly?
OS << "template ";
OS << Node->getMember().getAsString();
if (Node->hasExplicitTemplateArgumentList()) {
OS << TemplateSpecializationType::PrintTemplateArgumentList(
Node->getTemplateArgs(),
Node->getNumTemplateArgs(),
Policy);
}
}
static const char *getTypeTraitName(UnaryTypeTrait UTT) {

View File

@ -67,7 +67,7 @@ TemplateName::print(llvm::raw_ostream &OS, const PrintingPolicy &Policy,
OS << "template ";
OS << QTN->getDecl()->getNameAsString();
} else if (DependentTemplateName *DTN = getAsDependentTemplateName()) {
if (!SuppressNNS)
if (!SuppressNNS && DTN->getQualifier())
DTN->getQualifier()->print(OS, Policy);
OS << "template ";
// FIXME: Shouldn't we have a more general kind of name?

View File

@ -918,8 +918,10 @@ Parser::ParsePostfixExpressionSuffix(OwningExprResult LHS) {
ConsumeParen();
break;
}
case tok::arrow: // postfix-expression: p-e '->' identifier
case tok::period: { // postfix-expression: p-e '.' identifier
case tok::arrow:
case tok::period: {
// postfix-expression: p-e '->' template[opt] id-expression
// postfix-expression: p-e '.' template[opt] id-expression
tok::TokenKind OpKind = Tok.getKind();
SourceLocation OpLoc = ConsumeToken(); // Eat the "." or "->" token.

View File

@ -76,7 +76,9 @@ bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS,
if (HasScopeSpecifier) {
// C++ [basic.lookup.classref]p5:
// If the qualified-id has the form
//
// ::class-name-or-namespace-name::...
//
// the class-name-or-namespace-name is looked up in global scope as a
// class-name or namespace-name.
//

View File

@ -2029,14 +2029,17 @@ Sema::BuildMemberReferenceExpr(Scope *S, ExprArg Base, SourceLocation OpLoc,
FirstQualifierInScope = FindFirstQualifierInScope(S, Qualifier);
}
return Owned(new (Context) CXXUnresolvedMemberExpr(Context,
BaseExpr, true,
OpLoc,
Qualifier,
return Owned(CXXUnresolvedMemberExpr::Create(Context, BaseExpr, true,
OpLoc, Qualifier,
SS? SS->getRange() : SourceRange(),
FirstQualifierInScope,
MemberName,
MemberLoc));
FirstQualifierInScope,
MemberName,
MemberLoc,
HasExplicitTemplateArgs,
LAngleLoc,
ExplicitTemplateArgs,
NumExplicitTemplateArgs,
RAngleLoc));
}
else if (const PointerType *PT = BaseType->getAs<PointerType>())
BaseType = PT->getPointeeType();
@ -2067,14 +2070,19 @@ Sema::BuildMemberReferenceExpr(Scope *S, ExprArg Base, SourceLocation OpLoc,
FirstQualifierInScope = FindFirstQualifierInScope(S, Qualifier);
}
return Owned(new (Context) CXXUnresolvedMemberExpr(Context,
BaseExpr, false,
OpLoc,
Qualifier,
return Owned(CXXUnresolvedMemberExpr::Create(Context,
BaseExpr, false,
OpLoc,
Qualifier,
SS? SS->getRange() : SourceRange(),
FirstQualifierInScope,
MemberName,
MemberLoc));
FirstQualifierInScope,
MemberName,
MemberLoc,
HasExplicitTemplateArgs,
LAngleLoc,
ExplicitTemplateArgs,
NumExplicitTemplateArgs,
RAngleLoc));
}
}
}

View File

@ -1229,7 +1229,7 @@ Sema::ActOnMemberTemplateIdReferenceExpr(Scope *S, ExprArg Base,
else if (OverloadedFunctionDecl *Ovl = Template.getAsOverloadedFunctionDecl())
Name = Ovl->getDeclName();
else
assert(false && "Cannot support dependent template names yet");
Name = Template.getAsDependentTemplateName()->getName();
// Translate the parser's template argument list in our AST format.
llvm::SmallVector<TemplateArgument, 16> TemplateArgs;
@ -1287,11 +1287,6 @@ Sema::ActOnDependentTemplateName(SourceLocation TemplateKWLoc,
return Template;
}
// FIXME: We need to be able to create a dependent template name with just
// an identifier, to handle the x->template f<T> case.
assert(!ObjectType &&
"Cannot handle dependent template names without a nested-name-specifier");
NestedNameSpecifier *Qualifier
= static_cast<NestedNameSpecifier *>(SS.getScopeRep());
return TemplateTy::make(Context.getDependentTemplateName(Qualifier, &Name));

View File

@ -261,7 +261,8 @@ public:
/// By default, transforms the template name by transforming the declarations
/// and nested-name-specifiers that occur within the template name.
/// Subclasses may override this function to provide alternate behavior.
TemplateName TransformTemplateName(TemplateName Name);
TemplateName TransformTemplateName(TemplateName Name,
QualType ObjectType = QualType());
/// \brief Transform the given template argument.
///
@ -567,7 +568,8 @@ public:
/// template name. Subclasses may override this routine to provide different
/// behavior.
TemplateName RebuildTemplateName(NestedNameSpecifier *Qualifier,
const IdentifierInfo &II);
const IdentifierInfo &II,
QualType ObjectType);
/// \brief Build a new compound statement.
@ -1510,16 +1512,58 @@ public:
SS.setRange(QualifierRange);
SS.setScopeRep(Qualifier);
Base = SemaRef.BuildMemberReferenceExpr(/*Scope=*/0,
return SemaRef.BuildMemberReferenceExpr(/*Scope=*/0,
move(Base), OperatorLoc, OpKind,
MemberLoc,
Name,
/*FIXME?*/Sema::DeclPtrTy::make((Decl*)0),
&SS,
FirstQualifierInScope);
return move(Base);
}
/// \brief Build a new member reference expression with explicit template
/// arguments.
///
/// By default, performs semantic analysis to build the new expression.
/// Subclasses may override this routine to provide different behavior.
OwningExprResult RebuildCXXUnresolvedMemberExpr(ExprArg BaseE,
bool IsArrow,
SourceLocation OperatorLoc,
NestedNameSpecifier *Qualifier,
SourceRange QualifierRange,
TemplateName Template,
SourceLocation TemplateNameLoc,
NamedDecl *FirstQualifierInScope,
SourceLocation LAngleLoc,
const TemplateArgument *TemplateArgs,
unsigned NumTemplateArgs,
SourceLocation RAngleLoc) {
OwningExprResult Base = move(BaseE);
tok::TokenKind OpKind = IsArrow? tok::arrow : tok::period;
CXXScopeSpec SS;
SS.setRange(QualifierRange);
SS.setScopeRep(Qualifier);
// FIXME: We're going to end up looking up the template based on its name,
// twice! Also, duplicates part of Sema::ActOnMemberTemplateIdReferenceExpr.
DeclarationName Name;
if (TemplateDecl *ActualTemplate = Template.getAsTemplateDecl())
Name = ActualTemplate->getDeclName();
else if (OverloadedFunctionDecl *Ovl
= Template.getAsOverloadedFunctionDecl())
Name = Ovl->getDeclName();
else
Name = Template.getAsDependentTemplateName()->getName();
return SemaRef.BuildMemberReferenceExpr(/*Scope=*/0, move(Base),
OperatorLoc, OpKind,
TemplateNameLoc, Name, true,
LAngleLoc, TemplateArgs,
NumTemplateArgs, RAngleLoc,
Sema::DeclPtrTy(), &SS);
}
/// \brief Build a new Objective-C @encode expression.
///
/// By default, performs semantic analysis to build the new expression.
@ -1746,7 +1790,8 @@ TreeTransform<Derived>::TransformDeclarationName(DeclarationName Name,
template<typename Derived>
TemplateName
TreeTransform<Derived>::TransformTemplateName(TemplateName Name) {
TreeTransform<Derived>::TransformTemplateName(TemplateName Name,
QualType ObjectType) {
if (QualifiedTemplateName *QTN = Name.getAsQualifiedTemplateName()) {
NestedNameSpecifier *NNS
= getDerived().TransformNestedNameSpecifier(QTN->getQualifier(),
@ -1789,14 +1834,14 @@ TreeTransform<Derived>::TransformTemplateName(TemplateName Name) {
NestedNameSpecifier *NNS
= getDerived().TransformNestedNameSpecifier(DTN->getQualifier(),
/*FIXME:*/SourceRange(getDerived().getBaseLocation()));
if (!NNS)
if (!NNS && DTN->getQualifier())
return TemplateName();
if (!getDerived().AlwaysRebuild() &&
NNS == DTN->getQualifier())
return Name;
return getDerived().RebuildTemplateName(NNS, *DTN->getName());
return getDerived().RebuildTemplateName(NNS, *DTN->getName(), ObjectType);
}
if (TemplateDecl *Template = Name.getAsTemplateDecl()) {
@ -4195,6 +4240,9 @@ TreeTransform<Derived>::TransformCXXUnresolvedMemberExpr(
if (Base.isInvalid())
return SemaRef.ExprError();
// FIXME: The first qualifier found might be a template type parameter,
// in which case there is no transformed declaration to refer to (it might
// refer to a built-in type!).
NamedDecl *FirstQualifierInScope
= cast_or_null<NamedDecl>(
getDerived().TransformDecl(E->getFirstQualifierFoundInScope()));
@ -4214,21 +4262,60 @@ TreeTransform<Derived>::TransformCXXUnresolvedMemberExpr(
if (!Name)
return SemaRef.ExprError();
if (!getDerived().AlwaysRebuild() &&
Base.get() == E->getBase() &&
Qualifier == E->getQualifier() &&
Name == E->getMember() &&
FirstQualifierInScope == E->getFirstQualifierFoundInScope())
return SemaRef.Owned(E->Retain());
if (!E->hasExplicitTemplateArgumentList()) {
// This is a reference to a member without an explicitly-specified
// template argument list. Optimize for this common case.
if (!getDerived().AlwaysRebuild() &&
Base.get() == E->getBase() &&
Qualifier == E->getQualifier() &&
Name == E->getMember() &&
FirstQualifierInScope == E->getFirstQualifierFoundInScope())
return SemaRef.Owned(E->Retain());
return getDerived().RebuildCXXUnresolvedMemberExpr(move(Base),
E->isArrow(),
E->getOperatorLoc(),
Qualifier,
E->getQualifierRange(),
Name,
E->getMemberLoc(),
FirstQualifierInScope);
}
// FIXME: This is an ugly hack, which forces the same template name to
// be looked up multiple times. Yuck!
// FIXME: This also won't work for, e.g., x->template operator+<int>
TemplateName OrigTemplateName
= SemaRef.Context.getDependentTemplateName(0, Name.getAsIdentifierInfo());
TemplateName Template
= getDerived().TransformTemplateName(OrigTemplateName,
QualType::getFromOpaquePtr(ObjectType));
if (Template.isNull())
return SemaRef.ExprError();
llvm::SmallVector<TemplateArgument, 4> TransArgs;
for (unsigned I = 0, N = E->getNumTemplateArgs(); I != N; ++I) {
TemplateArgument TransArg
= getDerived().TransformTemplateArgument(E->getTemplateArgs()[I]);
if (TransArg.isNull())
return SemaRef.ExprError();
TransArgs.push_back(TransArg);
}
return getDerived().RebuildCXXUnresolvedMemberExpr(move(Base),
E->isArrow(),
E->getOperatorLoc(),
Qualifier,
E->getQualifierRange(),
Name,
Template,
E->getMemberLoc(),
FirstQualifierInScope);
FirstQualifierInScope,
E->getLAngleLoc(),
TransArgs.data(),
TransArgs.size(),
E->getRAngleLoc());
}
template<typename Derived>
@ -4643,33 +4730,18 @@ TreeTransform<Derived>::RebuildTemplateName(NestedNameSpecifier *Qualifier,
template<typename Derived>
TemplateName
TreeTransform<Derived>::RebuildTemplateName(NestedNameSpecifier *Qualifier,
const IdentifierInfo &II) {
if (Qualifier->isDependent())
return SemaRef.Context.getDependentTemplateName(Qualifier, &II);
// Somewhat redundant with ActOnDependentTemplateName.
const IdentifierInfo &II,
QualType ObjectType) {
CXXScopeSpec SS;
SS.setRange(SourceRange(getDerived().getBaseLocation()));
SS.setScopeRep(Qualifier);
Sema::TemplateTy Template;
TemplateNameKind TNK = SemaRef.isTemplateName(0, II,
/*FIXME:*/getDerived().getBaseLocation(),
&SS,
/*FIXME:ObjectType=*/0, false,
Template);
if (TNK == TNK_Non_template) {
SemaRef.Diag(getDerived().getBaseLocation(),
diag::err_template_kw_refers_to_non_template)
<< &II;
return TemplateName();
} else if (TNK == TNK_Function_template) {
SemaRef.Diag(getDerived().getBaseLocation(),
diag::err_template_kw_refers_to_non_template)
<< &II;
return TemplateName();
}
return Template.getAsVal<TemplateName>();
SS.setScopeRep(Qualifier);
return getSema().ActOnDependentTemplateName(
/*FIXME:*/getDerived().getBaseLocation(),
II,
/*FIXME:*/getDerived().getBaseLocation(),
SS,
ObjectType.getAsOpaquePtr())
.template getAsVal<TemplateName>();
}
template<typename Derived>

View File

@ -0,0 +1,30 @@
// RUN: clang-cc -fsyntax-only -verify %s
template<typename U, typename T>
U f0(T t) {
return t.template get<U>();
}
template<typename U, typename T>
int &f1(T t) {
// FIXME: When we pretty-print this, we lose the "template" keyword.
return t.U::template get<int&>();
}
struct X {
template<typename T> T get();
};
void test_f0(X x) {
int i = f0<int>(x);
int &ir = f0<int&>(x);
}
struct XDerived : public X {
};
void test_f1(XDerived xd) {
// FIXME: Not quite functional yet.
// int &ir = f1<X>(xd);
}