[翻译]More C++ Idioms 类成员检测器

译注 - 需要注意的是如果是用VC编译器,直接使用__if_exist关键字就行了,不需要用这种方法:

__if_exist(Class::member)
{
    //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可以是任何类型。

template<typename T>
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 };
};
//(
typedef 
DetectX type; 这个并没有被用到,删掉这行也没有关系)

这个惯用法的工作原理是:在编译期创建一个可控的二义性并通过SFINAE惯用法最终从其中恢复过来。第一个代理类Fallback有一个名字和你想要检测存在性的成员一样的成员。类Derived多继承自TFallback,这样它将有至少一个名为X的成员,如果T也有一个数据成员X的话将会有两个。

模板结构体Check被用来创建一个可控的二义性。Check需要两个模板参数,第一个是类型参数,第二个是一个该类型的实例,例如Check<int, 5>就是一个有效地实例化
(译注:注意例子中用到了指向成员的指针(Pointers to Members)int Fallback::*。两个名为func的重载函数创建了一个重载集合,这是SFINAE惯用法的通常做法。第一个func函数只有在数据成员U::X可以被无二义性的取得的情况下会被实例化。而U::X的地址只有在类Derived中只有一个名为X的数据成员时可以被取得。也就是说,此时T不会有名为X的数据成员。如果T含有XU::X的地址在没有更多排除歧义的信息的情况下是没法取得的,因此在没有错误的情况下对第一个func的实例化将会失败而另一个同名函数将会被选择。你应该注意到了两个func函数的返回值类型是不同的。第一个函数返回一个大小为1的数组的引用而第二个函数返回一个大小为2的数组的引用。通过返回值大小的不同可以用来检测哪个函数被实例化了。最终,一个布尔值被暴露出来(译注:声明为public的一个枚举值,值为0或者1,可当为布尔值使用)。当返回值的sizeof结果为2时该值为true,也就是说,只有当因为T含有名为X的数据成员而导致第二个函数func被实例化时为true。

对每一个需要检测的不同的数据成员,上面的模板需要相应的变化。这时使用一个宏将是一个好办法。下面的示例代码说明了宏的使用方法:

#define CREATE_MEMBER_DETECTOR(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<intdouble> Pair;
  std::cout << ((Detect_first<Pair>::value && Detect_second<Pair>::value)? "Pair" : "Not Pair");

 检测被重载的成员函数

一个成员检测器惯用法的变体可以用来检测一个类中特定成员函数的存在性,即使这个函数被重载了(译注:不被重载的当然也行,因为检测时函数的参数类型可以作为模板参数传入,所以可以区分不同的重载)也能被检测到。

template<typename T, typename RESULT, typename ARG1, typename ARG2>
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的成员函数,该函数有两个参数ARG1ARG2,返回值为RESULT。模板结构体Check只有在U含有U::policy成员函数,该函数有两个参数ARG1ARG2并且返回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 

原文地址:https://www.cnblogs.com/shawnhue/p/More_CPP_Idioms_Member_Detector.html