зеркало из https://github.com/microsoft/clang-1.git
Once we have deduced the template arguments of a class template
partial specialization, substitute those template arguments back into the template arguments of the class template partial specialization to see if the results still match the original template arguments. This code is more general than it needs to be, since we don't yet diagnose C++ [temp.class.spec]p9. However, it's likely to be needed for function templates. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@73196 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Родитель
3b56c00259
Коммит
02cbbd2d94
|
@ -1995,7 +1995,7 @@ public:
|
|||
bool CheckTemplateArgumentPointerToMember(Expr *Arg, NamedDecl *&Member);
|
||||
bool CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
||||
QualType InstantiatedParamType, Expr *&Arg,
|
||||
TemplateArgumentListBuilder *Converted = 0);
|
||||
TemplateArgument &Converted);
|
||||
bool CheckTemplateArgument(TemplateTemplateParmDecl *Param, DeclRefExpr *Arg);
|
||||
bool TemplateParameterListsAreEqual(TemplateParameterList *New,
|
||||
TemplateParameterList *Old,
|
||||
|
|
|
@ -297,7 +297,9 @@ void Sema::ActOnNonTypeTemplateParameterDefault(DeclPtrTy TemplateParamD,
|
|||
// FIXME: Implement this check! Needs a recursive walk over the types.
|
||||
|
||||
// Check the well-formedness of the default template argument.
|
||||
if (CheckTemplateArgument(TemplateParm, TemplateParm->getType(), Default)) {
|
||||
TemplateArgument Converted;
|
||||
if (CheckTemplateArgument(TemplateParm, TemplateParm->getType(), Default,
|
||||
Converted)) {
|
||||
TemplateParm->setInvalidDecl();
|
||||
return;
|
||||
}
|
||||
|
@ -1116,8 +1118,11 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template,
|
|||
|
||||
case TemplateArgument::Expression: {
|
||||
Expr *E = Arg.getAsExpr();
|
||||
if (CheckTemplateArgument(NTTP, NTTPType, E, &Converted))
|
||||
TemplateArgument Result;
|
||||
if (CheckTemplateArgument(NTTP, NTTPType, E, Result))
|
||||
Invalid = true;
|
||||
else
|
||||
Converted.push_back(Result);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1401,11 +1406,10 @@ Sema::CheckTemplateArgumentPointerToMember(Expr *Arg, NamedDecl *&Member) {
|
|||
/// InstantiatedParamType is the type of the non-type template
|
||||
/// parameter after it has been instantiated.
|
||||
///
|
||||
/// If Converted is non-NULL and no errors occur, the value
|
||||
/// of this argument will be added to the end of the Converted vector.
|
||||
/// If no error was detected, Converted receives the converted template argument.
|
||||
bool Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
||||
QualType InstantiatedParamType, Expr *&Arg,
|
||||
TemplateArgumentListBuilder *Converted) {
|
||||
TemplateArgument &Converted) {
|
||||
SourceLocation StartLoc = Arg->getSourceRange().getBegin();
|
||||
|
||||
// If either the parameter has a dependent type or the argument is
|
||||
|
@ -1413,8 +1417,7 @@ bool Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
|||
// FIXME: Add template argument to Converted!
|
||||
if (InstantiatedParamType->isDependentType() || Arg->isTypeDependent()) {
|
||||
// FIXME: Produce a cloned, canonical expression?
|
||||
if (Converted)
|
||||
Converted->push_back(TemplateArgument(Arg));
|
||||
Converted = TemplateArgument(Arg);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1479,7 +1482,7 @@ bool Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
|||
|
||||
QualType IntegerType = Context.getCanonicalType(ParamType);
|
||||
if (const EnumType *Enum = IntegerType->getAsEnumType())
|
||||
IntegerType = Enum->getDecl()->getIntegerType();
|
||||
IntegerType = Context.getCanonicalType(Enum->getDecl()->getIntegerType());
|
||||
|
||||
if (!Arg->isValueDependent()) {
|
||||
// Check that an unsigned parameter does not receive a negative
|
||||
|
@ -1509,21 +1512,19 @@ bool Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
|||
Value.setIsSigned(IntegerType->isSignedIntegerType());
|
||||
}
|
||||
|
||||
if (Converted) {
|
||||
// Add the value of this argument to the list of converted
|
||||
// arguments. We use the bitwidth and signedness of the template
|
||||
// parameter.
|
||||
if (Arg->isValueDependent()) {
|
||||
// The argument is value-dependent. Create a new
|
||||
// TemplateArgument with the converted expression.
|
||||
Converted->push_back(TemplateArgument(Arg));
|
||||
return false;
|
||||
}
|
||||
|
||||
Converted->push_back(TemplateArgument(StartLoc, Value,
|
||||
ParamType->isEnumeralType() ? ParamType : IntegerType));
|
||||
// Add the value of this argument to the list of converted
|
||||
// arguments. We use the bitwidth and signedness of the template
|
||||
// parameter.
|
||||
if (Arg->isValueDependent()) {
|
||||
// The argument is value-dependent. Create a new
|
||||
// TemplateArgument with the converted expression.
|
||||
Converted = TemplateArgument(Arg);
|
||||
return false;
|
||||
}
|
||||
|
||||
Converted = TemplateArgument(StartLoc, Value,
|
||||
ParamType->isEnumeralType() ? ParamType
|
||||
: IntegerType);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1590,11 +1591,8 @@ bool Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
|||
if (CheckTemplateArgumentPointerToMember(Arg, Member))
|
||||
return true;
|
||||
|
||||
if (Converted) {
|
||||
Member = cast_or_null<NamedDecl>(Context.getCanonicalDecl(Member));
|
||||
Converted->push_back(TemplateArgument(StartLoc, Member));
|
||||
}
|
||||
|
||||
Member = cast_or_null<NamedDecl>(Context.getCanonicalDecl(Member));
|
||||
Converted = TemplateArgument(StartLoc, Member);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1602,10 +1600,8 @@ bool Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
|||
if (CheckTemplateArgumentAddressOfObjectOrFunction(Arg, Entity))
|
||||
return true;
|
||||
|
||||
if (Converted) {
|
||||
Entity = cast_or_null<NamedDecl>(Context.getCanonicalDecl(Entity));
|
||||
Converted->push_back(TemplateArgument(StartLoc, Entity));
|
||||
}
|
||||
Entity = cast_or_null<NamedDecl>(Context.getCanonicalDecl(Entity));
|
||||
Converted = TemplateArgument(StartLoc, Entity);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1643,11 +1639,8 @@ bool Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
|||
if (CheckTemplateArgumentAddressOfObjectOrFunction(Arg, Entity))
|
||||
return true;
|
||||
|
||||
if (Converted) {
|
||||
Entity = cast_or_null<NamedDecl>(Context.getCanonicalDecl(Entity));
|
||||
Converted->push_back(TemplateArgument(StartLoc, Entity));
|
||||
}
|
||||
|
||||
Entity = cast_or_null<NamedDecl>(Context.getCanonicalDecl(Entity));
|
||||
Converted = TemplateArgument(StartLoc, Entity);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1687,11 +1680,8 @@ bool Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
|||
if (CheckTemplateArgumentAddressOfObjectOrFunction(Arg, Entity))
|
||||
return true;
|
||||
|
||||
if (Converted) {
|
||||
Entity = cast<NamedDecl>(Context.getCanonicalDecl(Entity));
|
||||
Converted->push_back(TemplateArgument(StartLoc, Entity));
|
||||
}
|
||||
|
||||
Entity = cast<NamedDecl>(Context.getCanonicalDecl(Entity));
|
||||
Converted = TemplateArgument(StartLoc, Entity);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1719,11 +1709,8 @@ bool Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
|
|||
if (CheckTemplateArgumentPointerToMember(Arg, Member))
|
||||
return true;
|
||||
|
||||
if (Converted) {
|
||||
Member = cast_or_null<NamedDecl>(Context.getCanonicalDecl(Member));
|
||||
Converted->push_back(TemplateArgument(StartLoc, Member));
|
||||
}
|
||||
|
||||
Member = cast_or_null<NamedDecl>(Context.getCanonicalDecl(Member));
|
||||
Converted = TemplateArgument(StartLoc, Member);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -531,30 +531,36 @@ Sema::DeduceTemplateArguments(ClassTemplatePartialSpecializationDecl *Partial,
|
|||
Deduced.data(), Deduced.size());
|
||||
if (Inst)
|
||||
return 0;
|
||||
|
||||
// FIXME: Substitute the deduced template arguments into the template
|
||||
// arguments of the class template partial specialization; the resulting
|
||||
// template arguments should match TemplateArgs exactly.
|
||||
|
||||
for (unsigned I = 0, N = Deduced.size(); I != N; ++I) {
|
||||
TemplateArgument &Arg = Deduced[I];
|
||||
|
||||
// FIXME: If this template argument was not deduced, but the corresponding
|
||||
// template parameter has a default argument, instantiate the default
|
||||
// argument.
|
||||
if (Arg.isNull()) // FIXME: Result->Destroy(Context);
|
||||
// C++ [temp.deduct.type]p2:
|
||||
// [...] or if any template argument remains neither deduced nor
|
||||
// explicitly specified, template argument deduction fails.
|
||||
TemplateArgumentListBuilder Builder(Context);
|
||||
for (unsigned I = 0, N = Deduced.size(); I != N; ++I) {
|
||||
if (Deduced[I].isNull())
|
||||
return 0;
|
||||
|
||||
|
||||
Builder.push_back(Deduced[I]);
|
||||
}
|
||||
|
||||
// Form the template argument list from the deduced template arguments.
|
||||
TemplateArgumentList *DeducedArgumentList
|
||||
= new (Context) TemplateArgumentList(Context, Builder, /*CopyArgs=*/true,
|
||||
/*FlattenArgs=*/true);
|
||||
|
||||
// Now that we have all of the deduced template arguments, take
|
||||
// another pass through them to convert any integral template
|
||||
// arguments to the appropriate type.
|
||||
for (unsigned I = 0, N = Deduced.size(); I != N; ++I) {
|
||||
TemplateArgument &Arg = Deduced[I];
|
||||
if (Arg.getKind() == TemplateArgument::Integral) {
|
||||
// FIXME: Instantiate the type, but we need some context!
|
||||
const NonTypeTemplateParmDecl *Parm
|
||||
= cast<NonTypeTemplateParmDecl>(Partial->getTemplateParameters()
|
||||
->getParam(I));
|
||||
// QualType T = InstantiateType(Parm->getType(), *Result,
|
||||
// Parm->getLocation(), Parm->getDeclName());
|
||||
// if (T.isNull()) // FIXME: Result->Destroy(Context);
|
||||
// return 0;
|
||||
QualType T = Parm->getType();
|
||||
QualType T = InstantiateType(Parm->getType(), *DeducedArgumentList,
|
||||
Parm->getLocation(), Parm->getDeclName());
|
||||
if (T.isNull()) // FIXME: DeducedArgumentList->Destroy(Context);
|
||||
return 0;
|
||||
|
||||
// FIXME: Make sure we didn't overflow our data type!
|
||||
llvm::APSInt &Value = *Arg.getAsIntegral();
|
||||
|
@ -564,14 +570,134 @@ Sema::DeduceTemplateArguments(ClassTemplatePartialSpecializationDecl *Partial,
|
|||
Value.setIsSigned(T->isSignedIntegerType());
|
||||
Arg.setIntegralType(T);
|
||||
}
|
||||
|
||||
(*DeducedArgumentList)[I] = Arg;
|
||||
}
|
||||
|
||||
// FIXME: This is terrible. DeduceTemplateArguments should use a
|
||||
// TemplateArgumentListBuilder directly.
|
||||
TemplateArgumentListBuilder Builder(Context);
|
||||
for (unsigned I = 0, N = Deduced.size(); I != N; ++I)
|
||||
Builder.push_back(Deduced[I]);
|
||||
|
||||
return new (Context) TemplateArgumentList(Context, Builder, /*CopyArgs=*/true,
|
||||
/*FlattenArgs=*/true);
|
||||
|
||||
// Substitute the deduced template arguments into the template
|
||||
// arguments of the class template partial specialization, and
|
||||
// verify that the instantiated template arguments are both valid
|
||||
// and are equivalent to the template arguments originally provided
|
||||
// to the class template.
|
||||
ClassTemplateDecl *ClassTemplate = Partial->getSpecializedTemplate();
|
||||
const TemplateArgumentList &PartialTemplateArgs = Partial->getTemplateArgs();
|
||||
for (unsigned I = 0, N = PartialTemplateArgs.flat_size(); I != N; ++I) {
|
||||
TemplateArgument InstArg = Instantiate(PartialTemplateArgs[I],
|
||||
*DeducedArgumentList);
|
||||
if (InstArg.isNull()) {
|
||||
// FIXME: DeducedArgumentList->Destroy(Context); (or use RAII)
|
||||
return 0;
|
||||
}
|
||||
|
||||
Decl *Param
|
||||
= const_cast<Decl *>(ClassTemplate->getTemplateParameters()->getParam(I));
|
||||
if (isa<TemplateTypeParmDecl>(Param)) {
|
||||
if (InstArg.getKind() != TemplateArgument::Type ||
|
||||
Context.getCanonicalType(InstArg.getAsType())
|
||||
!= Context.getCanonicalType(TemplateArgs[I].getAsType()))
|
||||
// FIXME: DeducedArgumentList->Destroy(Context); (or use RAII)
|
||||
return 0;
|
||||
} else if (NonTypeTemplateParmDecl *NTTP
|
||||
= dyn_cast<NonTypeTemplateParmDecl>(Param)) {
|
||||
QualType T = InstantiateType(NTTP->getType(), TemplateArgs,
|
||||
NTTP->getLocation(), NTTP->getDeclName());
|
||||
if (T.isNull())
|
||||
// FIXME: DeducedArgumentList->Destroy(Context); (or use RAII)
|
||||
return 0;
|
||||
|
||||
if (InstArg.getKind() == TemplateArgument::Declaration ||
|
||||
InstArg.getKind() == TemplateArgument::Expression) {
|
||||
// Turn the template argument into an expression, so that we can
|
||||
// perform type checking on it and convert it to the type of the
|
||||
// non-type template parameter. FIXME: Will this expression be
|
||||
// leaked? It's hard to tell, since our ownership model for
|
||||
// expressions in template arguments is so poor.
|
||||
Expr *E = 0;
|
||||
if (InstArg.getKind() == TemplateArgument::Declaration) {
|
||||
NamedDecl *D = cast<NamedDecl>(InstArg.getAsDecl());
|
||||
QualType T = Context.OverloadTy;
|
||||
if (ValueDecl *VD = dyn_cast<ValueDecl>(D))
|
||||
T = VD->getType().getNonReferenceType();
|
||||
E = new (Context) DeclRefExpr(D, T, InstArg.getLocation());
|
||||
} else {
|
||||
E = InstArg.getAsExpr();
|
||||
}
|
||||
|
||||
// Check that the template argument can be used to initialize
|
||||
// the corresponding template parameter.
|
||||
if (CheckTemplateArgument(NTTP, T, E, InstArg))
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (InstArg.getKind()) {
|
||||
case TemplateArgument::Null:
|
||||
assert(false && "Null template arguments cannot get here");
|
||||
return 0;
|
||||
|
||||
case TemplateArgument::Type:
|
||||
assert(false && "Type/value mismatch");
|
||||
return 0;
|
||||
|
||||
case TemplateArgument::Integral: {
|
||||
llvm::APSInt &Value = *InstArg.getAsIntegral();
|
||||
if (T->isIntegralType() || T->isEnumeralType()) {
|
||||
QualType IntegerType = Context.getCanonicalType(T);
|
||||
if (const EnumType *Enum = dyn_cast<EnumType>(IntegerType))
|
||||
IntegerType = Context.getCanonicalType(
|
||||
Enum->getDecl()->getIntegerType());
|
||||
|
||||
// Check that an unsigned parameter does not receive a negative
|
||||
// value.
|
||||
if (IntegerType->isUnsignedIntegerType()
|
||||
&& (Value.isSigned() && Value.isNegative()))
|
||||
return 0;
|
||||
|
||||
// Check for truncation. If the number of bits in the
|
||||
// instantiated template argument exceeds what is allowed by
|
||||
// the type, template argument deduction fails.
|
||||
unsigned AllowedBits = Context.getTypeSize(IntegerType);
|
||||
if (Value.getActiveBits() > AllowedBits)
|
||||
return 0;
|
||||
|
||||
if (Value.getBitWidth() != AllowedBits)
|
||||
Value.extOrTrunc(AllowedBits);
|
||||
Value.setIsSigned(IntegerType->isSignedIntegerType());
|
||||
|
||||
// Check that the instantiated value is the same as the
|
||||
// value provided as a template argument.
|
||||
if (Value != *TemplateArgs[I].getAsIntegral())
|
||||
return 0;
|
||||
} else if (T->isPointerType() || T->isMemberPointerType()) {
|
||||
// Deal with NULL pointers that are used to initialize
|
||||
// pointer and pointer-to-member non-type template
|
||||
// parameters (C++0x).
|
||||
if (TemplateArgs[I].getAsDecl())
|
||||
return 0; // Not a NULL declaration
|
||||
|
||||
// Check that the integral value is 0, the NULL pointer
|
||||
// constant.
|
||||
if (Value != 0)
|
||||
return 0;
|
||||
} else
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case TemplateArgument::Declaration:
|
||||
if (Context.getCanonicalDecl(InstArg.getAsDecl())
|
||||
!= Context.getCanonicalDecl(TemplateArgs[I].getAsDecl()))
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case TemplateArgument::Expression:
|
||||
// FIXME: Check equality of expressions
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
assert(isa<TemplateTemplateParmDecl>(Param));
|
||||
// FIXME: Check template template arguments
|
||||
}
|
||||
}
|
||||
|
||||
return DeducedArgumentList;
|
||||
}
|
||||
|
|
|
@ -185,18 +185,12 @@ void Sema::PrintInstantiationStack() {
|
|||
case ActiveTemplateInstantiation::PartialSpecDeductionInstantiation: {
|
||||
ClassTemplatePartialSpecializationDecl *PartialSpec
|
||||
= cast<ClassTemplatePartialSpecializationDecl>((Decl *)Active->Entity);
|
||||
std::string TemplateArgsStr
|
||||
= TemplateSpecializationType::PrintTemplateArgumentList(
|
||||
PartialSpec->getTemplateArgs().getFlatArgumentList(),
|
||||
PartialSpec->getTemplateArgs().flat_size(),
|
||||
Context.PrintingPolicy);
|
||||
// FIXME: The active template instantiation's template arguments
|
||||
// are interesting, too. We should add something like [with T =
|
||||
// foo, U = bar, etc.] to the string.
|
||||
Diags.Report(FullSourceLoc(Active->PointOfInstantiation, SourceMgr),
|
||||
diag::note_partial_spec_deduct_instantiation_here)
|
||||
<< (PartialSpec->getSpecializedTemplate()->getNameAsString() +
|
||||
TemplateArgsStr)
|
||||
<< Context.getTypeDeclType(PartialSpec)
|
||||
<< Active->InstantiationRange;
|
||||
break;
|
||||
}
|
||||
|
@ -441,7 +435,7 @@ InstantiateFunctionProtoType(const FunctionProtoType *T,
|
|||
ParamTypes.push_back(P);
|
||||
}
|
||||
|
||||
return SemaRef.BuildFunctionType(ResultType, &ParamTypes[0],
|
||||
return SemaRef.BuildFunctionType(ResultType, ParamTypes.data(),
|
||||
ParamTypes.size(),
|
||||
T->isVariadic(), T->getTypeQuals(),
|
||||
Loc, Entity);
|
||||
|
@ -567,7 +561,7 @@ InstantiateTemplateSpecializationType(
|
|||
TemplateArgs);
|
||||
|
||||
return SemaRef.CheckTemplateIdType(Name, Loc, SourceLocation(),
|
||||
&InstantiatedTemplateArgs[0],
|
||||
InstantiatedTemplateArgs.data(),
|
||||
InstantiatedTemplateArgs.size(),
|
||||
SourceLocation());
|
||||
}
|
||||
|
|
|
@ -224,6 +224,8 @@ int is_member_function_pointer3[
|
|||
int is_member_function_pointer4[
|
||||
is_member_function_pointer<int (X::*)(float) const>::value? 1 : -1];
|
||||
|
||||
// Test substitution of non-dependent arguments back into the template
|
||||
// argument list of the class template partial specialization.
|
||||
template<typename T, typename ValueType = T>
|
||||
struct is_nested_value_type_identity {
|
||||
static const bool value = false;
|
||||
|
@ -245,11 +247,56 @@ struct HasIdentityValueType {
|
|||
|
||||
struct NoValueType { };
|
||||
|
||||
// FIXME: Need substitution into the template arguments of the partial spec
|
||||
//int is_nested_value_type_identity0[
|
||||
// is_nested_value_type_identity<HasValueType<int> >::value? -1 : 1];
|
||||
int is_nested_value_type_identity0[
|
||||
is_nested_value_type_identity<HasValueType<int> >::value? -1 : 1];
|
||||
int is_nested_value_type_identity1[
|
||||
is_nested_value_type_identity<HasIdentityValueType>::value? 1 : -1];
|
||||
// FIXME: Enable when we have SFINAE support
|
||||
//int is_nested_value_type_identity0[
|
||||
//int is_nested_value_type_identity2[
|
||||
// is_nested_value_type_identity<NoValueType>::value? -1 : 1];
|
||||
|
||||
// FIXME: The tests that follow are stress-tests for the substitution
|
||||
// of deduced template arguments into the template argument list of a
|
||||
// partial specialization. I believe that we'll need this code for
|
||||
// substitution into function templates, but note that the examples
|
||||
// below are ill-formed and should eventually be removed.
|
||||
template<typename T, int N>
|
||||
struct is_sizeof_T {
|
||||
static const bool value = false;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct is_sizeof_T<T, sizeof(T)> {
|
||||
static const bool value = true;
|
||||
};
|
||||
|
||||
int is_sizeof_T0[is_sizeof_T<int, sizeof(int)>::value? 1 : -1];
|
||||
int is_sizeof_T1[is_sizeof_T<int, sizeof(char)>::value? -1 : 1];
|
||||
|
||||
template<typename T>
|
||||
struct HasStaticOfT {
|
||||
static T value;
|
||||
static T other_value;
|
||||
};
|
||||
|
||||
struct DerivedStaticOfInt : HasStaticOfT<int> { };
|
||||
|
||||
template<typename X, typename T, T *Ptr>
|
||||
struct is_static_int_val {
|
||||
static const bool value = false;
|
||||
};
|
||||
|
||||
template<typename X, typename T>
|
||||
struct is_static_int_val<X, T, &X::value> {
|
||||
static const bool value = true;
|
||||
};
|
||||
|
||||
int is_static_int_val0[
|
||||
is_static_int_val<HasStaticOfT<int>, int,
|
||||
&HasStaticOfT<int>::value>::value ? 1 : -1];
|
||||
int is_static_int_val1[
|
||||
is_static_int_val<HasStaticOfT<int>, int,
|
||||
&HasStaticOfT<int>::other_value>::value ? -1 : 1];
|
||||
int is_static_int_val2[
|
||||
is_static_int_val<DerivedStaticOfInt, int,
|
||||
&HasStaticOfT<int>::value>::value ? 1 : -1];
|
||||
|
|
Загрузка…
Ссылка в новой задаче