您的位置:首页 > 编程语言 > C语言/C++

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

2015-12-09 09:43 489 查看
前几天看了《C++11之美》受到一些启发,想到可以通过判断一个类型是否有指定的操作符(比如==,>=)。

基本的原理与文中的差不多,利用SFINAE原则,通过返回类型后置来推断表达式的类型,推断的过程中利用
declval
,它可以获取类型的右值引用,以便来调用
==
操作符,这个过程是在编译期完成的。

如果通过
==
操作符比较
declval
的右值引用成功了,则会继续推断逗号表达式的类型,最终推断的函数返回类型为
bool


如果通过
==
操作符比较
declval
的右值引用失败了,则推断失败,编译器会选择优先级最低的
test(...)
函数,它的返回类型为
void


我们最后判断实例化的
test<T>(0)
的返回值是否为
bool
,可以知道类型T是否存在
==
操作符。

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


在上面代码中,推导
test(int)
返回类型的表达式是由执行
==
操作符比较两个
declval
获取的右值引用来实现的。有两种方式
declval<U>()==declval<U>()
declval<U>().operator==(declval<U>())


第一种是真接按常用的
==
操作符用法写的
==
表达式,第二种则是把操作符
==
作为一个类成员函数来调用。两种表达式判断是有区别的:

第一种方式可以用于判断基本数据类型和class类型。

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

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

下面是完整的代码

#include <iostream>
#include <type_traits>
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<U>()==declval<T>());
//template<typename U> static auto test(int)->  decltype(declval<U>().operator==(declval<T>()));
template<typename U> static void test(...);
enum{value=std::is_same<decltype(test<T>(0)), bool>::value};
//通过判断test<T>(0)返回值是否为bool来判断是否有==操作符
};
template <typename T>
struct has_asterisk_operator{
template<typename U> static auto test(int)->    decltype(*declval<U>());
template<typename U> static void test(...);
enum{value=std::is_reference<decltype(test<T>(0))>::value};
//通过判断test<T>(0)返回值是否为引用来判断是否有*操作符
};
template <typename T>
struct has_gt_operator{
template<typename U> static auto test(int)->    decltype(declval<U>()>declval<U>());
template<typename U> static void test(...);
enum{value=std::is_same<decltype(test<T>(0)), bool>::value};
//通过判断test<T>(0)返回值是否为bool来判断是否有>操作符
};

int main()
{
cout<<"int has operator> :"<<has_gt_operator<int>::value<<endl;
cout<<"int* has operator> :"<<has_gt_operator<int*>::value<<endl;
cout<<"test_class has operator> :"<<has_gt_operator<test_classA>::value<<endl;

cout<<"int has operator* :"<<has_asterisk_operator<int>::value<<endl;
cout<<"int* has operator* :"<<has_asterisk_operator<int*>::value<<endl;

cout<<"int has operator== :"<<has_equal_operator<int>::value<<endl;
cout<<"test_class has operator== :"<<has_equal_operator<test_classA>::value<<endl;
cout<<"test_classC has operator ==:"<<has_equal_operator<test_classC>::value<<endl;
cout<<"hasequal<double> has operator== :"<<has_equal_operator<has_equal_operator<double>>::value<<endl;
}


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

/* 判断obj1,obj2是否相等
* 如果K有==操作符则使用==比较版本,否则使用default_equals函数进行二进制比较
*/
template<typename _K=K>
typename std::enable_if<!has_equal_operator<_K>::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<has_equal_operator<_K>::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;
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息