aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/clang/AST/TemplateBase.h1
-rw-r--r--include/clang/Basic/DiagnosticSemaKinds.td2
-rw-r--r--lib/AST/ASTContext.cpp7
-rw-r--r--lib/AST/ASTImporter.cpp2
-rw-r--r--lib/AST/DumpXML.cpp3
-rw-r--r--lib/AST/ExprConstant.cpp6
-rw-r--r--lib/AST/ItaniumMangle.cpp14
-rw-r--r--lib/AST/TemplateBase.cpp35
-rw-r--r--lib/AST/TypePrinter.cpp5
-rw-r--r--lib/Sema/SemaExpr.cpp3
-rw-r--r--lib/Sema/SemaTemplate.cpp108
-rw-r--r--lib/Sema/SemaTemplateDeduction.cpp10
-rw-r--r--lib/Sema/SemaTemplateInstantiate.cpp24
-rw-r--r--test/CXX/temp/temp.arg/temp.arg.nontype/p1-11.cpp55
-rw-r--r--test/CodeGenCXX/mangle-nullptr-arg.cpp13
-rw-r--r--test/FixIt/fixit-cxx0x.cpp4
16 files changed, 231 insertions, 61 deletions
diff --git a/include/clang/AST/TemplateBase.h b/include/clang/AST/TemplateBase.h
index 06e4181e71..65f54600c0 100644
--- a/include/clang/AST/TemplateBase.h
+++ b/include/clang/AST/TemplateBase.h
@@ -101,7 +101,6 @@ public:
/// declaration, which is either an external declaration or a
/// template declaration.
TemplateArgument(Decl *D) : Kind(Declaration) {
- // FIXME: Need to be sure we have the "canonical" declaration!
TypeOrValue = reinterpret_cast<uintptr_t>(D);
}
diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td
index 6735bfcb45..ffe7be300a 100644
--- a/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2339,6 +2339,8 @@ def err_template_arg_not_integral_or_enumeral : Error<
def err_template_arg_not_ice : Error<
"non-type template argument of type %0 is not an integral constant "
"expression">;
+def err_template_arg_untyped_null_constant : Error<
+ "null non-type template argument must be cast to template parameter type %0">;
def err_deduced_non_type_template_arg_type_mismatch : Error<
"deduced non-type template argument does not have the same type as the "
"its corresponding template parameter (%0 vs %1)">;
diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp
index 15f85ba9c4..acf5e0bbc9 100644
--- a/lib/AST/ASTContext.cpp
+++ b/lib/AST/ASTContext.cpp
@@ -3313,8 +3313,11 @@ ASTContext::getCanonicalTemplateArgument(const TemplateArgument &Arg) const {
case TemplateArgument::Expression:
return Arg;
- case TemplateArgument::Declaration:
- return TemplateArgument(Arg.getAsDecl()->getCanonicalDecl());
+ case TemplateArgument::Declaration: {
+ if (Decl *D = Arg.getAsDecl())
+ return TemplateArgument(D->getCanonicalDecl());
+ return TemplateArgument((Decl*)0);
+ }
case TemplateArgument::Template:
return TemplateArgument(getCanonicalTemplateName(Arg.getAsTemplate()));
diff --git a/lib/AST/ASTImporter.cpp b/lib/AST/ASTImporter.cpp
index 621658c7f0..3879907ec6 100644
--- a/lib/AST/ASTImporter.cpp
+++ b/lib/AST/ASTImporter.cpp
@@ -325,6 +325,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return IsSameValue(*Arg1.getAsIntegral(), *Arg2.getAsIntegral());
case TemplateArgument::Declaration:
+ if (!Arg1.getAsDecl() || !Arg2.getAsDecl())
+ return !Arg1.getAsDecl() && !Arg2.getAsDecl();
return Context.IsStructurallyEquivalent(Arg1.getAsDecl(), Arg2.getAsDecl());
case TemplateArgument::Template:
diff --git a/lib/AST/DumpXML.cpp b/lib/AST/DumpXML.cpp
index b180e808ab..4c7cd8a679 100644
--- a/lib/AST/DumpXML.cpp
+++ b/lib/AST/DumpXML.cpp
@@ -320,7 +320,8 @@ struct XMLDumper : public XMLDeclVisitor<XMLDumper>,
break;
case TemplateArgument::Declaration: {
- visitDeclRef(A.getAsDecl());
+ if (Decl *D = A.getAsDecl())
+ visitDeclRef(D);
break;
}
case TemplateArgument::Integral: {
diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp
index 0d8490e137..4ef169d189 100644
--- a/lib/AST/ExprConstant.cpp
+++ b/lib/AST/ExprConstant.cpp
@@ -1743,8 +1743,10 @@ static bool HandleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv,
// parameters are constant expressions even if they're non-const.
// In C, such things can also be folded, although they are not ICEs.
const VarDecl *VD = dyn_cast<VarDecl>(D);
- if (const VarDecl *VDef = VD->getDefinition(Info.Ctx))
- VD = VDef;
+ if (VD) {
+ if (const VarDecl *VDef = VD->getDefinition(Info.Ctx))
+ VD = VDef;
+ }
if (!VD || VD->isInvalidDecl()) {
Info.Diag(Conv);
return false;
diff --git a/lib/AST/ItaniumMangle.cpp b/lib/AST/ItaniumMangle.cpp
index 5457036920..d7b6354540 100644
--- a/lib/AST/ItaniumMangle.cpp
+++ b/lib/AST/ItaniumMangle.cpp
@@ -3118,12 +3118,22 @@ void CXXNameMangler::mangleTemplateArg(const NamedDecl *P,
case TemplateArgument::Declaration: {
assert(P && "Missing template parameter for declaration argument");
// <expr-primary> ::= L <mangled-name> E # external name
-
+ // <expr-primary> ::= L <type> 0 E
// Clang produces AST's where pointer-to-member-function expressions
// and pointer-to-function expressions are represented as a declaration not
// an expression. We compensate for it here to produce the correct mangling.
- NamedDecl *D = cast<NamedDecl>(A.getAsDecl());
const NonTypeTemplateParmDecl *Parameter = cast<NonTypeTemplateParmDecl>(P);
+
+ // Handle NULL pointer arguments.
+ if (!A.getAsDecl()) {
+ Out << "L";
+ mangleType(Parameter->getType());
+ Out << "0E";
+ break;
+ }
+
+
+ NamedDecl *D = cast<NamedDecl>(A.getAsDecl());
bool compensateMangling = !Parameter->getType()->isReferenceType();
if (compensateMangling) {
Out << 'X';
diff --git a/lib/AST/TemplateBase.cpp b/lib/AST/TemplateBase.cpp
index 7e6bae2b26..531e03e302 100644
--- a/lib/AST/TemplateBase.cpp
+++ b/lib/AST/TemplateBase.cpp
@@ -80,9 +80,13 @@ bool TemplateArgument::isDependent() const {
return true;
case Declaration:
- if (DeclContext *DC = dyn_cast<DeclContext>(getAsDecl()))
- return DC->isDependentContext();
- return getAsDecl()->getDeclContext()->isDependentContext();
+ if (Decl *D = getAsDecl()) {
+ if (DeclContext *DC = dyn_cast<DeclContext>(D))
+ return DC->isDependentContext();
+ return D->getDeclContext()->isDependentContext();
+ }
+
+ return false;
case Integral:
// Never dependent
@@ -118,10 +122,13 @@ bool TemplateArgument::isInstantiationDependent() const {
return true;
case Declaration:
- if (DeclContext *DC = dyn_cast<DeclContext>(getAsDecl()))
- return DC->isDependentContext();
- return getAsDecl()->getDeclContext()->isDependentContext();
-
+ if (Decl *D = getAsDecl()) {
+ if (DeclContext *DC = dyn_cast<DeclContext>(D))
+ return DC->isDependentContext();
+ return D->getDeclContext()->isDependentContext();
+ }
+ return false;
+
case Integral:
// Never dependent
return false;
@@ -322,16 +329,14 @@ void TemplateArgument::print(const PrintingPolicy &Policy,
}
case Declaration: {
- bool Unnamed = true;
if (NamedDecl *ND = dyn_cast_or_null<NamedDecl>(getAsDecl())) {
if (ND->getDeclName()) {
- Unnamed = false;
Out << *ND;
+ } else {
+ Out << "<anonymous>";
}
- }
-
- if (Unnamed) {
- Out << "<anonymous>";
+ } else {
+ Out << "nullptr";
}
break;
}
@@ -488,7 +493,9 @@ const DiagnosticBuilder &clang::operator<<(const DiagnosticBuilder &DB,
return DB << Arg.getAsType();
case TemplateArgument::Declaration:
- return DB << Arg.getAsDecl();
+ if (Decl *D = Arg.getAsDecl())
+ return DB << D;
+ return DB << "nullptr";
case TemplateArgument::Integral:
return DB << Arg.getAsIntegral()->toString(10);
diff --git a/lib/AST/TypePrinter.cpp b/lib/AST/TypePrinter.cpp
index 664a658160..3bf80e7972 100644
--- a/lib/AST/TypePrinter.cpp
+++ b/lib/AST/TypePrinter.cpp
@@ -264,8 +264,9 @@ void TypePrinter::printRValueReference(const RValueReferenceType *T,
void TypePrinter::printMemberPointer(const MemberPointerType *T,
std::string &S) {
- std::string C;
- print(QualType(T->getClass(), 0), C);
+ PrintingPolicy InnerPolicy(Policy);
+ Policy.SuppressTag = true;
+ std::string C = QualType(T->getClass(), 0).getAsString(InnerPolicy);
C += "::*";
S = C + S;
diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp
index 82d23783f9..fea0fe1e62 100644
--- a/lib/Sema/SemaExpr.cpp
+++ b/lib/Sema/SemaExpr.cpp
@@ -10480,7 +10480,8 @@ namespace {
bool MarkReferencedDecls::TraverseTemplateArgument(
const TemplateArgument &Arg) {
if (Arg.getKind() == TemplateArgument::Declaration) {
- S.MarkAnyDeclReferenced(Loc, Arg.getAsDecl());
+ if (Decl *D = Arg.getAsDecl())
+ S.MarkAnyDeclReferenced(Loc, D);
}
return Inherited::TraverseTemplateArgument(Arg);
diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp
index 4f6c879317..14558a695c 100644
--- a/lib/Sema/SemaTemplate.cpp
+++ b/lib/Sema/SemaTemplate.cpp
@@ -3527,20 +3527,21 @@ CheckTemplateArgumentAddressOfObjectOrFunction(Sema &S,
dyn_cast<SubstNonTypeTemplateParmExpr>(Arg))
Arg = subst->getReplacement()->IgnoreImpCasts();
- DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Arg);
- if (!DRE) {
- S.Diag(Arg->getLocStart(), diag::err_template_arg_not_decl_ref)
- << Arg->getSourceRange();
- S.Diag(Param->getLocation(), diag::note_template_param_here);
- return true;
- }
-
// Stop checking the precise nature of the argument if it is value dependent,
// it should be checked when instantiated.
if (Arg->isValueDependent()) {
Converted = TemplateArgument(ArgIn);
return false;
}
+
+ DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Arg);
+ if (!DRE) {
+ S.Diag(Arg->getLocStart(), diag::err_template_arg_not_decl_ref)
+ << Arg->getSourceRange();
+ S.Diag(Param->getLocation(), diag::note_template_param_here);
+ return true;
+ }
+
if (!isa<ValueDecl>(DRE->getDecl())) {
S.Diag(Arg->getLocStart(),
@@ -4048,21 +4049,74 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
QualType ArgType = Arg->getType();
DeclAccessPair FoundResult; // temporary for ResolveOverloadedFunction
- // C++0x [temp.arg.nontype]p5 bullets 2, 4 and 6 permit conversion
- // from a template argument of type std::nullptr_t to a non-type
- // template parameter of type pointer to object, pointer to
- // function, or pointer-to-member, respectively.
- if (ArgType->isNullPtrType()) {
- if (ParamType->isPointerType() || ParamType->isMemberPointerType()) {
- Converted = TemplateArgument((NamedDecl *)0);
- return Owned(Arg);
+ // C++11 [temp.arg.nontype]p1:
+ // - a constant expression that evaluates to a null pointer value (4.10); or
+ // - a constant expression that evaluates to a null member pointer value
+ // (4.11); or
+ // - an address constant expression of type std::nullptr_t
+ if (getLangOpts().CPlusPlus0x &&
+ (ParamType->isPointerType() || ParamType->isMemberPointerType() ||
+ ParamType->isNullPtrType()) &&
+ !Arg->isValueDependent() && !Arg->isTypeDependent()) {
+ if (Expr::NullPointerConstantKind NPC
+ = Arg->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent)){
+ if (NPC != Expr::NPCK_CXX0X_nullptr) {
+ // C++11 [temp.arg.nontype]p5b2:
+ // if the template-argument is of type std::nullptr_t, the null
+ // pointer conversion (4.10) is applied. [ Note: In particular,
+ // neither the null pointer conversion for a zero-valued integral
+ // constant expression (4.10) nor the derived-to-base conversion
+ // (4.10) are applied. Although 0 is a valid template-argument for a
+ // non-type template-parameter of integral type, it is not a valid
+ // template-argument for a non-type template-parameter of pointer
+ // type. However, both (int*)0 and nullptr are valid
+ // template-arguments for a non-type template-parameter of type
+ // "pointer to int." — end note ]
+ bool ObjCLifetimeConversion;
+ if (!Context.hasSameUnqualifiedType(ArgType, ParamType) &&
+ !IsQualificationConversion(ArgType, ParamType, false,
+ ObjCLifetimeConversion)) {
+ {
+ SemaDiagnosticBuilder DB
+ = Diag(Arg->getExprLoc(),
+ diag::err_template_arg_untyped_null_constant);
+ DB << ParamType;
+
+ if (ArgType->isIntegralType(Context)) {
+ std::string Code = "(" + ParamType.getAsString() + ")";
+ DB << FixItHint::CreateInsertion(Arg->getLocStart(), Code);
+ }
+ }
+ Diag(Param->getLocation(), diag::note_template_param_here);
+ }
+ }
+
+ Converted = TemplateArgument((Decl *)0);
+ return false;
}
-
- if (ParamType->isNullPtrType()) {
- llvm::APSInt Zero(Context.getTypeSize(Context.NullPtrTy), true);
- Converted = TemplateArgument(Zero, Context.NullPtrTy);
- return Owned(Arg);
+
+ // Check for a null (member) pointer value.
+ Expr::EvalResult EvalResult;
+ if (Arg->EvaluateAsRValue(EvalResult, Context) &&
+ ((EvalResult.Val.isLValue() && !EvalResult.Val.getLValueBase()) ||
+ (EvalResult.Val.isMemberPointer() &&
+ !EvalResult.Val.getMemberPointerDecl()))) {
+ Converted = TemplateArgument((Decl *)0);
+ return false;
+ }
+ }
+
+ // If we haven't dealt with a null pointer-typed parameter yet, do so now.
+ if (ParamType->isNullPtrType()) {
+ if (Arg->isTypeDependent() || Arg->isValueDependent()) {
+ Converted = TemplateArgument(Arg);
+ return false;
}
+
+ Diag(Arg->getExprLoc(), diag::err_template_arg_not_convertible)
+ << Arg->getType() << ParamType;
+ Diag(Param->getLocation(), diag::note_template_param_here);
+ return true;
}
// Handle pointer-to-function, reference-to-function, and
@@ -4255,6 +4309,18 @@ Sema::BuildExpressionFromDeclTemplateArgument(const TemplateArgument &Arg,
SourceLocation Loc) {
assert(Arg.getKind() == TemplateArgument::Declaration &&
"Only declaration template arguments permitted here");
+
+ // For a NULL non-type template argument, return nullptr casted to the
+ // parameter's type.
+ if (!Arg.getAsDecl()) {
+ return ImpCastExprToType(
+ new (Context) CXXNullPtrLiteralExpr(Context.NullPtrTy, Loc),
+ ParamType,
+ ParamType->getAs<MemberPointerType>()
+ ? CK_NullToMemberPointer
+ : CK_NullToPointer);
+ }
+
ValueDecl *VD = cast<ValueDecl>(Arg.getAsDecl());
if (VD->getDeclContext()->isRecord() &&
diff --git a/lib/Sema/SemaTemplateDeduction.cpp b/lib/Sema/SemaTemplateDeduction.cpp
index b448633381..2ea1e6ff93 100644
--- a/lib/Sema/SemaTemplateDeduction.cpp
+++ b/lib/Sema/SemaTemplateDeduction.cpp
@@ -1586,8 +1586,7 @@ DeduceTemplateArguments(Sema &S,
case TemplateArgument::Declaration:
if (Arg.getKind() == TemplateArgument::Declaration &&
- Param.getAsDecl()->getCanonicalDecl() ==
- Arg.getAsDecl()->getCanonicalDecl())
+ isSameDeclaration(Param.getAsDecl(), Arg.getAsDecl()))
return Sema::TDK_Success;
Info.FirstArg = Param;
@@ -1858,8 +1857,7 @@ static bool isSameTemplateArg(ASTContext &Context,
Context.getCanonicalType(Y.getAsType());
case TemplateArgument::Declaration:
- return X.getAsDecl()->getCanonicalDecl() ==
- Y.getAsDecl()->getCanonicalDecl();
+ return isSameDeclaration(X.getAsDecl(), Y.getAsDecl());
case TemplateArgument::Template:
case TemplateArgument::TemplateExpansion:
@@ -1925,7 +1923,7 @@ getTrivialTemplateArgumentLoc(Sema &S,
case TemplateArgument::Declaration: {
Expr *E
= S.BuildExpressionFromDeclTemplateArgument(Arg, NTTPType, Loc)
- .takeAs<Expr>();
+ .takeAs<Expr>();
return TemplateArgumentLoc(TemplateArgument(E), E);
}
@@ -4410,7 +4408,7 @@ MarkUsedTemplateParameters(ASTContext &Ctx,
switch (TemplateArg.getKind()) {
case TemplateArgument::Null:
case TemplateArgument::Integral:
- case TemplateArgument::Declaration:
+ case TemplateArgument::Declaration:
break;
case TemplateArgument::Type:
diff --git a/lib/Sema/SemaTemplateInstantiate.cpp b/lib/Sema/SemaTemplateInstantiate.cpp
index 307cccce8b..4740145fd5 100644
--- a/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1113,15 +1113,21 @@ ExprResult TemplateInstantiator::transformNonTypeTemplateParmRef(
type = argExpr->getType();
} else if (arg.getKind() == TemplateArgument::Declaration) {
- ValueDecl *VD = cast<ValueDecl>(arg.getAsDecl());
-
- // Find the instantiation of the template argument. This is
- // required for nested templates.
- VD = cast_or_null<ValueDecl>(
- getSema().FindInstantiatedDecl(loc, VD, TemplateArgs));
- if (!VD)
- return ExprError();
-
+ ValueDecl *VD;
+ if (Decl *D = arg.getAsDecl()) {
+ VD = cast<ValueDecl>(D);
+
+ // Find the instantiation of the template argument. This is
+ // required for nested templates.
+ VD = cast_or_null<ValueDecl>(
+ getSema().FindInstantiatedDecl(loc, VD, TemplateArgs));
+ if (!VD)
+ return ExprError();
+ } else {
+ // Propagate NULL template argument.
+ VD = 0;
+ }
+
// Derive the type we want the substituted decl to have. This had
// better be non-dependent, or these checks will have serious problems.
if (parm->isExpandedParameterPack()) {
diff --git a/test/CXX/temp/temp.arg/temp.arg.nontype/p1-11.cpp b/test/CXX/temp/temp.arg/temp.arg.nontype/p1-11.cpp
new file mode 100644
index 0000000000..d72f26ecdb
--- /dev/null
+++ b/test/CXX/temp/temp.arg/temp.arg.nontype/p1-11.cpp
@@ -0,0 +1,55 @@
+// RUN: %clang_cc1 -std=c++11 %s -verify
+
+namespace std {
+ typedef decltype(nullptr) nullptr_t;
+}
+
+template<int *ip> struct IP { // expected-note 2 {{template parameter is declared here}}
+ IP<ip> *ip2;
+};
+
+constexpr std::nullptr_t get_nullptr() { return nullptr; }
+
+std::nullptr_t np;
+
+IP<0> ip0; // expected-error{{null non-type template argument must be cast to template parameter type 'int *'}}
+IP<(0)> ip1; // expected-error{{null non-type template argument must be cast to template parameter type 'int *'}}
+IP<nullptr> ip2;
+IP<get_nullptr()> ip3;
+IP<(int*)0> ip4;
+IP<np> ip5;
+
+struct X { };
+template<int X::*pm> struct PM { // expected-note 2 {{template parameter is declared here}}
+ PM<pm> *pm2;
+};
+
+PM<0> pm0; // expected-error{{null non-type template argument must be cast to template parameter type 'int X::*'}}
+PM<(0)> pm1; // expected-error{{null non-type template argument must be cast to template parameter type 'int X::*'}}
+PM<nullptr> pm2;
+PM<get_nullptr()> pm3;
+PM<(int X::*)0> pm4;
+PM<np> pm5;
+
+template<int (X::*pmf)(int)> struct PMF { // expected-note 2 {{template parameter is declared here}}
+ PMF<pmf> *pmf2;
+};
+
+PMF<0> pmf0; // expected-error{{null non-type template argument must be cast to template parameter type 'int (X::*)(int)'}}
+PMF<(0)> pmf1; // expected-error{{null non-type template argument must be cast to template parameter type 'int (X::*)(int)'}}
+PMF<nullptr> pmf2;
+PMF<get_nullptr()> pmf3;
+PMF<(int (X::*)(int))0> pmf4;
+PMF<np> pmf5;
+
+
+template<std::nullptr_t np> struct NP { // expected-note 2{{template parameter is declared here}}
+ NP<np> *np2;
+};
+
+NP<nullptr> np1;
+NP<np> np2;
+NP<get_nullptr()> np3;
+NP<0> np4; // expected-error{{null non-type template argument must be cast to template parameter type 'std::nullptr_t' (aka 'nullptr_t')}}
+constexpr int i = 7;
+NP<i> np5; // expected-error{{non-type template argument of type 'const int' cannot be converted to a value of type 'std::nullptr_t'}}
diff --git a/test/CodeGenCXX/mangle-nullptr-arg.cpp b/test/CodeGenCXX/mangle-nullptr-arg.cpp
new file mode 100644
index 0000000000..393de0b0ec
--- /dev/null
+++ b/test/CodeGenCXX/mangle-nullptr-arg.cpp
@@ -0,0 +1,13 @@
+// RUN: %clang_cc1 -std=c++11 -emit-llvm -o - %s | FileCheck %s
+
+template<int *ip> struct IP {};
+
+// CHECK: define void @_Z5test12IPILPi0EE
+void test1(IP<nullptr>) {}
+
+struct X{ };
+template<int X::*pm> struct PM {};
+
+// CHECK: define void @_Z5test22PMILM1Xi0EE
+void test2(PM<nullptr>) { }
+
diff --git a/test/FixIt/fixit-cxx0x.cpp b/test/FixIt/fixit-cxx0x.cpp
index 997d73abfb..b6cc2c08b0 100644
--- a/test/FixIt/fixit-cxx0x.cpp
+++ b/test/FixIt/fixit-cxx0x.cpp
@@ -104,3 +104,7 @@ namespace TestMisplacedEllipsisRecovery {
template<template<typename> ...Foo, // expected-error {{template template parameter requires 'class' after the parameter list}}
template<template<template<typename>>>> // expected-error 3 {{template template parameter requires 'class' after the parameter list}}
void func();
+
+template<int *ip> struct IP { }; // expected-note{{declared here}}
+IP<0> ip0; // expected-error{{null non-type template argument must be cast to template parameter type 'int *'}}
+