如何判断一个类型是否可比较
对于一个用户定义类型,如果定义了operator==或者operator<等比较函数,那么这个类型就是可比较的。在实现一些工具库的时候,可能需要知道某个类型是否可比较,例如,想要知道是否可以使用==操作符来比较两个类对象是否相等,那么可以使用下面的模板类来判断:
1 | template<typename T> |
使用方式:
1 | //内置类型和标准库类型 |
这个模板类型基于C++的SFINAE特性,关于这个特性的解释,可以参考之前的文章《如何判断一个容器是否关联容器》,在这篇文章里,也基于SFINAE特性实现了一个判断某类型是否关联容器的模板类。
IsEqualityComparable使用了std::is_same_v来推导结果,它检查Test<T>(nullptr)这个函数调用表达式的返回值类型是否bool,如果是,则T可以用==进行比较,如果不是则不可以比较。
为了让Test<T>(nullptr)这个表达式对不同类型有不同的返回值,这里定义了两个重载的Test模板方法,其中第一个的声明如下所示:
1 | template<typename K> |
这个方法的返回值类型通过decltype来推导,而推导的来源正是模板类型K的==表达式,如果K可以比较,那么这个方法是有效的,返回值类型是bool;如果K不可以比较,那么根据SFINAE,这个方法会被删除,就像从未存在过一样。注意std::declval的使用,这个函数可以在不调用构造函数的前提下生成一个K的引用,以便使用者直接访问K的接口。这意味着使用者不需要知道如何构造K的对象,因为==只能作用于对象上,在比较之前必须先有对象,为了创建对象,使用者要调用K的构造方法。由于每个类型的构造方法都不一样,这里就没办法做到广泛适用。而std::declval绕过了构造方法,避免了这个问题。
第二个重载的Test模板方法如下所示:
1 | template<typename K> |
这个方法是“没有选择时的最后选择“。如果第一个模板方法被删除了,那么Test<T>(nullptr)这个表达式就会匹配到这个模板方法,表达式的返回值类型是int;如果第一个模板方法有效,那么表达式会优先匹配第一个方法,因为参数为...的重载方法优先级是最低的,此时表达式的返回值类型是bool。