译注 - 需要注意的是如果是用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<int, double> 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/archive/2011/11/29/More_CPP_Idioms_Member_Detector.html

[翻译]More C++ Idioms - 类成员检测器相关推荐

  1. c ++类成员函数_仅使用C ++创建具有公共数据成员的类

    c ++类成员函数 Let's understand 让我们来了解 What is data member? 什么是数据成员? Any variable declared inside the cla ...

  2. c ++类成员函数_C ++编程中的数据成员和成员函数

    c ++类成员函数 C ++中的数据成员和成员函数 (Data members and Member functions in C++) "Data Member" and &qu ...

  3. 【转载】c++之类的基本操作(c++ primer 的读书笔记 ,类对象, 类用户, 类成员的含义)

    一前言 看c++ primer有一个地方看的云里雾里的,这么一段话 可以认为 protected 访问标号是 private 和 public 的混合: • 像 private 成员一样,protec ...

  4. 一家之言:说说 JavaScript 的类成员的访问控制

    看书的时候遇到这样一个问题,程序代码如下 var ob = function(){ var obj = this; function fn1(){ alert( obj === window );// ...

  5. C++ 笔记(16)— 类和对象(类定义、类实例对象定义、访问类成员、类成员函数、类 public/private/protected 成员、类对象引用和指针)

    1. 类的定义 类定义是以关键字 class 开头,后跟类的名称.并在它后面依次包含类名,一组放在 {} 内的成员属性和成员函数,以及结尾的分号. 类声明将类本身及其属性告诉编译器.类声明本身并不能改 ...

  6. (原创)c#学习笔记10--定义类成员03--接口的实现01--显示实现接口成员

    10.3  接口的实现 在继续前,先讨论一下如何定义和实现接口.第9章介绍了接口定义的方式与类相似,使用的代码如下: interface IMyInterface {// Interface memb ...

  7. php 访问类成员,PHP类成员的访问方式和权限_PHP教程

    PHP5的访问方式允许限制对类成员的访问. 这是在PHP5中新增的功能,但在许多面向对象语言中都早已存在. 有了访问方式,才能开发一个可靠的面向对象应用程序,并且构建可重用的面向对象类库. 像C++和 ...

  8. 37、C++ Primer 4th笔记,特殊工具与技术,类成员指针

    1.成员指针(pointer to member)包含类的类型以及成员的类型.成员指针只应用于类的非static成员.static类成员不是任何对象的组成部分,所以不需要特殊语法来指向static成员 ...

  9. C++类成员函数重载问题

    C++类成员函数重载问题 #include <iostream> using namespace std; class A {private: int a;int b; public:A( ...

最新文章

  1. python坐标点怎么输入_python导入坐标点的具体操作
  2. [开源]C#二维码生成解析工具,可添加自定义Logo
  3. python【数据结构与算法】二分模板
  4. python好还是c语言好-初学者先学习python语言还是C语言
  5. python中requests的常用方法_Python爬虫简介(2)——请求库的常用方法及使用,python,入门,二,requests,常见,和,库中,文官,网...
  6. 大咖微课 | 直面Angular2系列课第二期开讲
  7. Navicat for MySQL v8.0.27 的注册码
  8. Redis配置文件常用配置消息解说--版本5.0.9
  9. jTip定制实现博客日历
  10. 测试用例的设计方法及例子
  11. 4W字全面解读数据中台、数据仓库和数据湖
  12. CuteEditor 6.0 在线HTML编辑器的领航者
  13. Office文档修复介绍之:laola文件格式介绍
  14. k6前级效果器怎么用_K6效果器功能说明
  15. Speex manul中文版
  16. 云计算机基础架构,云计算基础架构的解决方案
  17. 搞明白activated和deactivated
  18. 决策树之C4.5实现(离散属性与连续,属性并存)
  19. 工作流之Camunda开发记录(一)----常用API与流程
  20. ST_TileEnvelope函数矢量切片

热门文章

  1. LeetCode 911. 在线选举(二分查找)
  2. LeetCode MySQL 619. 只出现一次的最大数字
  3. LeetCode 895. 最大频率栈(哈希+按频数存储)
  4. LeetCode 223. 矩形面积
  5. 程序员面试金典 - 面试题 10.02. 变位词组(哈希map)
  6. linux发指令给网络设备,Linux学习笔记五(网络命令)
  7. python语言画心_python语言还是java如何用python画爱心
  8. python concat_python-pd.concat()不合并在同一索引上
  9. 计算机应用基础形考作业3Excel部分,计算机应用基础形考3,Excel部分
  10. 遍历删除_面试难题:List 如何一边遍历,一边删除?