模板特化、类型萃取

最近在项目中用到了类型萃取,写一文章记录一下

类型萃取解决了什么问题?

  • 如何在编译期间判断某个类型 T 是否是 class 类
  • 判断某个 class T 是否有指定的成员变量或成员函数?
    要了解类型萃取,首先要学一下模版特化

模板特化

有时为了需要,针对特定的类型,需要对模板进行特化,也就是所谓的特殊处理。比如有以下代码:

template<typename C> 
int test(C c) {
    cout<<"for all type"<<endl;
    return c.size();
}

我们写了一个模板函数test,如果我们在main函数中执行如下语句

int main() {
    string s = "sss";
    test<string>(s);//正确,输出“for all type”
    test<int>(1);    //报错,int型没有size()
    return 0;
}

显然test<int>(1); 会报错,因为int型并没有size()方法,此时我们再写一个也叫test的函数:

//为int提供的特化版本test函数
template<typename C> 
int test(int i) {
    cout<<"for int type"<<endl;
    return 0;
}

我们将此称为test函数的int型特化版本,我们希望如果test()方法接收到int类型的参数会调用该特化版本,相当于给int型开了个小灶,如果我们再次执行之前的main 函数

int main() {
    string s = "sss";
    test<string>(s);//正确,输出“for all type”
    test<int>(1);    //不会报错了,输出"for int type"
    return 0;
}

此时,test<int>(1)不会报错了,而是输出"for int type"。
我们把这叫做模板特化,告诉编译器对于某种类型进行特殊输处理(开小灶),上述例子中我们对int型进行了特殊处理,此时如果我们在main函数中执行

 test<double>(1);    //报错,没对double型特殊处理,double型没有size()方法

还是会报错,因为我们并没有double型的特化
下面将介绍模板特化具体能解决什么问题

判断类型 T 是否是一个class

我们想在编译期间判断一个类型T是否是一个类,套用 SFINAE 原则,我们需要两个返回不同值的模板函数

  • 当传入 Class 作为模板实参时匹配到其中一个函数模板
  • 而传入其他实参时匹配到另一个。这样我们就可以根据返回值将 class 与其他类型区分开。
    先上主要部分代码
template<typename T>
class IsClass {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    template<typename C> static Two test(...);
  public:
    static bool value ;//= sizeof(IsClass<T>::test<T>(NULL)) == 1;
};
template<typename T>
bool IsClass<T>::value = sizeof(IsClass<T>::test<T>(NULL)) == 1;

这里解释一下,

  • int C::*表示指向class C的成员的指针
  • sizeof ()操作符可以在编译期间返回指定的类型占用的空间大小
    我们再随便定义一个类Test
class Test {
};

在main函数里执行下列语句语句

int main() {
  bool is_class = IsClass<Test>::value;
  std::cout << is_class << value;  //Test是一个class,输出1
  bool is_class2 = IsClass<int>::value;
  std::cout << is_class2 << value;  //int不是一个类,输出0
}

解释一下原因,对于语句

bool is_class = IsClass<T>::value;
  • 如果T不是一个类,比如T是一个int型,在计算value时会调用返回Two的test函。因为返回One的参数是 int C::*,int型不具有指向其函数的指针,编译器匹配不上。
  • 如果T是一个类,能够匹配上返回One的test函数,NULL能够被推导成int T::*。
  • 有同学会问,T是一个类,也能匹配上返回One的test方法,怎么在二者之间做选择呢?《C++ templates》书中说道:

overload resolution prefers the conversion from zero to a null pointer constant over binding an argument to an ellipsis parameter (ellipsis parameters are the weakest kind of binding from an overload resolution perspective).

意思就是:省略号参数是最弱的绑定类型,所以最后只有当其他都匹配不上才会去匹配参数为省略号的函数

判断class T 是否有某个成员函数

假设有Man和Woman两个类:

class Man {
public:
    void man_do(){}
    static void sayhi() {
        cout << "im a man" << endl;
    }
};

class Woman {
public:
    void woman_do(){}
    static void sayhi() {
        cout << "im a woman" << endl;
    }
};

我们有如下main函数:

int main()
{
    Say<Man>();
    Say<Woman>();
    return 0;
}

我们希望Say<T>()函数能够通过判断T类型有没有一个叫做"man_do()"的方法,如果有,就执行man::sayhi(),没有就执行woman::sayhi()。
来看具体实现,先定义Say函数,Say()函数会调用Check结构体的type类的sayhi()方法,Say的定义如下:

template<class P>
void Say() {
    Check<P>::type::sayhi();
}

再来看Check结构体和IF结构体的内容:

template <class P>
struct Check {
    typedef typename IF<Trait<P>::has_mando, Man, Woman>::type type;
};
template <bool C, class T, class E>
struct IF { typedef T type; };              //非特化IF
template <class T, class E>
struct IF<false, T, E> { typedef E type; }; //标明了false,特化版本的IF

可以看出,IF的type决定了typename的type,如果IF<>模板第一个参数是false,Check的type就是Woman,如果IF的模板第一个参数是true,那么Check的type就是Man。关键就在于传给IF第一个参数的Trait<P>::has_mando,来看定义:

template <class P, class... Args>
struct Trait {
    static const bool has_mando = HasMando<P>::value;
};

Trait有一个成员变量has_mando,由HasMando<P>::value决定,再来看HasMando结构体

template <class T>
struct HasMando {
    typedef char yes[1];
    typedef char no[2];
    template <class>   static no& test(...);
    template <class U> static yes& test(decltype(&U::man_do));
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};

这是不是和上一节的方法很像?是通过sizeof()+匹配不同函数实现判断。
现在从底往上推导
如果类T有man_do方法,会匹配上返回yes的test()方法,因此HasMando<T>结构体的value就等于true。
再来看Trait的定义

template <class P, class... Args>
struct Trait {
    static const bool has_mando = HasMando<P>::value;
};

Trait的has_mando等于HasMando<P>的value,因此也等于true
再往上到了Check和IF:

template <class P>
struct Check {
    typedef typename IF<Trait<P>::has_mando, Man, Woman>::type type;
};

template <bool C, class T, class E>
struct IF { typedef T type; };              //非特化IF
template <class T, class E>
struct IF<false, T, E> { typedef E type; }; //标明了false,特化版本的IF

IF有特化和非特化两个版本,Trait<P>::has_mando是true,匹配上非特化版本的IF模板,因此IF的type为Man,Check的type也为man

最后,Say函数调用Man的sayhi()函数

template<class P>
void Say() {
    Check<P>::type::sayhi();
}

check<P>的type是Man,调用Man的sayhi(),结束

最后把所有代码放在这里

#include <iostream>
using namespace std;

class Man {
public:
    void man_do(){}
    static void sayhi() {
        cout << "im a man" << endl;
    }
};

class Woman {
public:
    void woman_do(){}
    static void sayhi() {
        cout << "im a woman" << endl;
    }
};

template <class T>
struct HasMando {
    typedef char yes[1];
    typedef char no[2];
    template <class>   static no& test(...);
    template <class U> static yes& test(decltype(&U::man_do));
    static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};

template <class P, class... Args>
struct Trait {
    static const bool has_mando = HasMando<P>::value;
};

template <bool C, class T, class E>
struct IF { typedef T type; };
template <class T, class E>
struct IF<false, T, E> { typedef E type; };

template <class P>
struct Check {
    typedef typename IF<Trait<P>::has_mando, Man, Woman>::type type;
};

template<class P>
void Say() {
    Check<P>::type::sayhi();
}
int main()
{
    Say<Man>();
    Say<Woman>();
    return 0;
}

参考:http://kaiyuan.me/2018/05/08/sfinae/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容