前几天看了《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;
};