译注 - 需要注意的是如果是用VC编译器,直接使用__if_exist关键字就行了,不需要用这种方法:
{
//do_something
}
__if_exist(Class::method)
{
//do_something
}
目的
检测一个特定类成员的存在性。
别称
动机
编译期的反射能力是C++模板元编程的基础。 诸如Boost.TypeTraits和TR1 <type_traits> header的类型特征(Type traits)库提供了强大的方法来分离类型和他们的关系的信息。检测一个类的数据成员的存在性也是编译期反射的一个例子。
解决方案和示例代码
成员检测器惯用法(idiom)通过"匹配失败不是错误"(Substitution Failure Is Not An Error-SFINAE)惯用法实现。下面的模板类DetectX<T>是一个可以检测类型T是不是有一个名为X的数据成员的元函数。注意数据成员X可以是任何类型。
class DetectX
{
struct Fallback { int X; }; // add member name "X"
struct Derived : T, Fallback { };
template<typename U, U> struct Check;
typedef char ArrayOfOne[1]; // typedef for an array of size one.
typedef char ArrayOfTwo[2]; // typedef for an array of size two.
template<typename U>
static ArrayOfOne & func(Check<int Fallback::*, &U::X> *);
template<typename U>
static ArrayOfTwo & func(...);
public:
typedef DetectX type;
enum { value = sizeof(func<Derived>(0)) == 2 };
};
这个惯用法的工作原理是:在编译期创建一个可控的二义性并通过SFINAE惯用法最终从其中恢复过来。第一个代理类Fallback有一个名字和你想要检测存在性的成员一样的成员。类Derived多继承自T和Fallback,这样它将有至少一个名为X的成员,如果T也有一个数据成员X的话将会有两个。
对每一个需要检测的不同的数据成员,上面的模板需要相应的变化。这时使用一个宏将是一个好办法。下面的示例代码说明了宏的使用方法:
template<typename T> class Detect_##X { \
struct Fallback { int X; }; \
struct Derived : T, Fallback { }; \
\
template<typename U, U> struct Check; \
\
typedef char ArrayOfOne[1]; \
typedef char ArrayOfTwo[2]; \
\
template<typename U> static ArrayOfOne & func(Check<int Fallback::*, &U::X> *); \
template<typename U> static ArrayOfTwo & func(...); \
public: \
typedef Detect_##X type; \
enum { value = sizeof(func<Derived>(0)) == 2 }; \
};
CREATE_MEMBER_DETECTOR(first);
CREATE_MEMBER_DETECTOR(second);
int main(void)
{
typedef std::pair<int, double> Pair;
std::cout << ((Detect_first<Pair>::value && Detect_second<Pair>::value)? "Pair" : "Not Pair");
}
检测被重载的成员函数
一个成员检测器惯用法的变体可以用来检测一个类中特定成员函数的存在性,即使这个函数被重载了(译注:不被重载的当然也行,因为检测时函数的参数类型可以作为模板参数传入,所以可以区分不同的重载)也能被检测到。
class HasPolicy
{
template <typename U, RESULT (U::*)(ARG1, ARG2)> struct Check;
template <typename U> static char func(Check<U, &U::policy> *);
template <typename U> static int func(...);
public:
typedef HasMember type;
enum { value = sizeof(func<T>(0)) == sizeof(char) };
};
//(typedef HasMember type; 似乎是个笔误,但是删掉这行也没有关系)
上面的模板类HasPolicy检查U是否拥有一个名为policy的成员函数,该函数有两个参数ARG1和ARG2,返回值为RESULT。模板结构体Check只有在U含有U::policy成员函数,该函数有两个参数ARG1和ARG2并且返回RESULT时才会实例化成功。 注意模板结构体Check的第一个模板参数是一个类型,第二个模板参数是指向该类型的成员函数的指针。如果模板结构体Check不能被实例化,剩下的以int为返回值的func将会被实例化。func函数返回值类型的大小最终决定类型特征的答案:true或者false.
已知应用
相关惯用法
Substitution Failure Is Not An Error (SFINAE)
参考资料
Substitution failure is not an error, part II
原文链接
http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Member_Detector