如何编写通用相等比较函数:优先用std::cmp_equal,失败回退operator==
通用相等检查函数的跨编译器兼容实现
我需要实现一个通用相等检查函数,优先使用std::cmp_equal(因其特性),对std::cmp_equal不支持的类型(如bool或自定义类型)则回退使用operator==。原本以为用requires表达式可轻松实现,但发现该写法在libc中有效,在libstdc中却触发static_assert错误。
代码示例:
#include <utility> template <typename T, typename U> auto generic_equal(T t, U u) { if constexpr (requires { std::cmp_equal(t, u); }) { return std::cmp_equal(t, u); } else { return t == u; } } struct A { friend bool operator==(A,A) = default; }; int main() { generic_equal(false, false); generic_equal(-1, 5); generic_equal(A{}, A{}); }
核心问题:如何让编译器在requires阶段就判断std::cmp_equal是否可实例化,而非在调用时才报错?
解决方案
问题根源在于libstdc++对std::cmp_equal的实现逻辑:当传入不支持的类型时,它会直接触发static_assert,而非让SFINAE机制生效。以下两种方法可以解决这个问题:
方法一:通过辅助模板利用SFINAE筛选合法调用
编写一个辅助模板,仅当std::cmp_equal可合法实例化时才启用对应的重载,否则匹配到兜底重载:
#include <utility> #include <type_traits> namespace detail { template <typename T, typename U> auto try_cmp_equal(T t, U u) -> decltype(std::cmp_equal(t, u)) { return std::cmp_equal(t, u); } // 兜底重载,当std::cmp_equal不可用时匹配此版本 template <typename...> std::false_type try_cmp_equal(...); } template <typename T, typename U> auto generic_equal(T t, U u) { if constexpr (std::is_same_v<decltype(detail::try_cmp_equal(t, u)), bool>) { return detail::try_cmp_equal(t, u); } else { return t == u; } } struct A { friend bool operator==(A,A) = default; }; int main() { generic_equal(false, false); generic_equal(-1, 5); generic_equal(A{}, A{}); }
方法二:改进requires表达式避免触发static_assert
在requires中使用std::declval生成类型占位符,同时明确检查返回类型是否可转换为bool,这样编译器只会检查std::cmp_equal的签名合法性,不会实例化函数体从而避免触发static_assert:
#include <utility> #include <type_traits> template <typename T, typename U> auto generic_equal(T t, U u) { if constexpr (requires { { std::cmp_equal(std::declval<T>(), std::declval<U>()) } -> std::convertible_to<bool>; }) { return std::cmp_equal(t, u); } else { return t == u; } } struct A { friend bool operator==(A,A) = default; }; int main() { generic_equal(false, false); generic_equal(-1, 5); generic_equal(A{}, A{}); }
原理说明
- 方法一借助SFINAE:当
std::cmp_equal不可用时,detail::try_cmp_equal的第一个重载会因实例化失败被排除,转而匹配返回std::false_type的兜底重载,从而正确回退到operator==。 - 方法二优化requires检查:使用
std::declval避免传入真实参数触发函数体实例化,同时通过返回类型约束确保std::cmp_equal的调用是合法的,让libstdc++的static_assert不会被触发。
内容的提问来源于stack exchange,提问作者Kyle Markley
相关产品推荐
相关产品推荐

