如何判断一个类型是否可比较
对于一个用户定义类型,如果定义了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
。