C++11 元编程(meta-programming)判断T是否有==操作符

前几天看了《C++11之美》受到一些启发,想到可以通过判断一个类型是否有指定的操作符(比如==,>=)。
基本的原理与文中的差不多,利用SFINAE原则,通过返回类型后置来推断表达式的类型,推断的过程中利用declval,它可以获取类型的右值引用,以便来调用==操作符,这个过程是在编译期完成的。
如果通过==操作符比较declval的右值引用成功了,则会继续推断逗号表达式的类型,最终推断的函数返回类型为bool
如果通过==操作符比较declval的右值引用失败了,则推断失败,编译器会选择优先级最低的test(...)函数,它的返回类型为void
我们最后判断实例化的test(0)的返回值是否为bool,可以知道类型T是否存在==操作符。

template <typename T>
struct has_equal_operator{
    template<typename U>  static auto test(int)->   decltype(declval()==declval());
    //template static auto test(int)-> decltype(declval().operator==(declval()));
    template<typename U> static void test(...);
    enum{value=std::is_same<decltype(test(0)), bool>::value};
};

在上面代码中,推导test(int)返回类型的表达式是由执行==操作符比较两个declval获取的右值引用来实现的。有两种方式 declval()==declval()declval().operator==(declval())
第一种是真接按常用的==操作符用法写的==表达式,第二种则是把操作符==作为一个类成员函数来调用。两种表达式判断是有区别的:

第一种方式可以用于判断基本数据类型和class类型。
对于基本数据类型(比如int),因为没有成员函数,所以第二种方式对于基本类型返回的肯定是false.无法用这种方式判断基本数据类型是否有==操作符,只适用于class类型。

基于上面这个元函数的原理,我们还可以继续写出其他操作符的判断函数,比如>,*操作符。
下面是完整的代码

#include 
#include 
using namespace std;

struct  test_classA{
    int a;
    virtual bool operator==(const test_classA&v){
        return a==v.a;
    }
    virtual ~test_classA()=default;
};
struct test_classB:test_classA{
};
struct test_classC:test_classB{
};
template <typename T>
struct has_equal_operator{
    template<typename U>  static auto test(int)->   decltype(declval()==declval());
    //template static auto test(int)-> decltype(declval().operator==(declval()));
    template<typename U> static void test(...);
    enum{value=std::is_same<decltype(test(0)), bool>::value};
    //通过判断test(0)返回值是否为bool来判断是否有==操作符
};
template <typename T>
struct has_asterisk_operator{
    template<typename U> static auto test(int)->    decltype(*declval());
    template<typename U> static void test(...);
    enum{value=std::is_reference<decltype(test(0))>::value};
    //通过判断test(0)返回值是否为引用来判断是否有*操作符
};
template <typename T>
struct has_gt_operator{
    template<typename U> static auto test(int)->    decltype(declval()>declval());
    template<typename U> static void test(...);
    enum{value=std::is_same<decltype(test(0)), bool>::value};
    //通过判断test(0)返回值是否为bool来判断是否有>操作符
};

int main()
{
    cout<<"int has operator> :"<int>::value<cout<<"int* has operator> :"<int*>::value<cout<<"test_class has operator> :"<::value<cout<<"int has operator* :"<int>::value<cout<<"int* has operator* :"<int*>::value<cout<<"int has operator== :"<int>::value<cout<<"test_class has operator== :"<::value<cout<<"test_classC has operator ==:"<::value<cout<<"hasequal has operator== :"<double>>::value<

下面是has_equal_operator的使用场景的例子:

    /* 判断obj1,obj2是否相等 * 如果K有==操作符则使用==比较版本,否则使用default_equals函数进行二进制比较 */
    template<typename _K=K>
    typename std::enable_if::value,bool>::type equals(const _K &obj1, const _K &obj2)const {
        return 0 == default_equals(&obj1, &obj2, sizeof(_K));
    }
    template<typename _K=K>
    typename std::enable_if::value,bool>::type equals(const _K &obj1, const _K &obj2)const {
        return obj1==obj2;
    }

后记:

本文在C++论坛发出后,经网友akirya提醒才知道 std::is_assignable其实就是采用本文类似的原理。
看来还是对STL提供的元函数不熟悉,否则如果早想到看看std::is_assignable的源码,就不会花这么时间了。
下面是gcc的std::is_assignable相关源码:

  template<typename _Tp, typename _Up>
    class __is_assignable_helper
    {
      template<typename _Tp1, typename _Up1,
           typename = decltype(declval<_Tp1>() = declval<_Up1>())>
    static true_type
    __test(int);

      template<typename, typename>
    static false_type
    __test(...);

    public:
      typedef decltype(__test<_Tp, _Up>(0)) type;
    };

你可能感兴趣的:(c/c++/c++11)