如何准确检测包含boost::multiindex的类是否具备可比较性?
问题
我写了一个模板函数compare,意图检测类是否具备可比较性,仅在可比较时执行比较逻辑:
template <typename T> int compare(const T& a, const T& b) { if constexpr (std::three_way_comparable<T>) { std::cerr << "comparable " << typeid(T).name() << "\n"; if (a == b) { return 0; } if (a < b) { return -1; } if (a > b) { return 1; } } else { std::cerr << "not comparable " << typeid(T).name() << "\n"; } return 0; }
该函数在简单结构体场景下运行正常:
struct CompStruct { int a {}; std::string b; auto operator<=>(const CompStruct&) const = default; }; struct UncompStruct { int a {}; std::string b; auto operator<=>(const UncompStruct&) const = delete; }; CompStruct cs_a, cs_b; compare(cs_a, cs_b); // 输出"comparable" UncompStruct ucs_a, ucs_b; compare(ucs_a, ucs_b); // 输出"not comparable"
但当使用嵌套了不可比较内部类型的boost::multiindex时,代码编译失败:
namespace bmi = boost::multi_index; using MultiIndex = bmi::multi_index_container< UncompStruct, bmi::indexed_by<bmi::random_access<>>>; struct bad_type : std::map<std::string, MultiIndex> {}; bad_type bt_a; bad_type bt_b; compare(bt_a, bt_b); // 编译失败
将multiindex内部的UncompStruct替换为CompStruct时则编译正常。现在需要解决:如何真正检测一个类是否具备可比较性?
解决方案
问题根源在于std::three_way_comparable仅检查类型是否存在符合要求的比较运算符声明,不会验证这些运算符的实际可调用性。而Boost.MultiIndex的比较运算符是模板化的——它不会直接禁用运算符,只会在尝试比较内部不可比较的元素时才触发编译错误,因此std::three_way_comparable会误判bad_type为可比较类型,进而进入错误分支触发编译失败。
要实现真实可比较性检测,需要自定义概念,直接验证比较表达式的合法性与可调用性,而非仅检查概念满足:
#include <concepts> #include <type_traits> #include <iostream> #include <string> #include <map> #include <boost/multi_index_container.hpp> #include <boost/multi_index/random_access_index.hpp> template <typename T> concept ActuallyComparable = requires(const T& a, const T& b) { { a == b } -> std::convertible_to<bool>; { a < b } -> std::convertible_to<bool>; { a > b } -> std::convertible_to<bool>; }; template <typename T> int compare(const T& a, const T& b) { if constexpr (ActuallyComparable<T>) { std::cerr << "comparable " << typeid(T).name() << "\n"; if (a == b) { return 0; } if (a < b) { return -1; } if (a > b) { return 1; } } else { std::cerr << "not comparable " << typeid(T).name() << "\n"; } return 0; } // 原结构体定义 struct CompStruct { int a {}; std::string b; auto operator<=>(const CompStruct&) const = default; }; struct UncompStruct { int a {}; std::string b; auto operator<=>(const UncompStruct&) const = delete; }; // Boost.MultiIndex相关定义 namespace bmi = boost::multi_index; using MultiIndex = bmi::multi_index_container< UncompStruct, bmi::indexed_by<bmi::random_access<>>>; struct bad_type : std::map<std::string, MultiIndex> {}; int main() { CompStruct cs_a, cs_b; compare(cs_a, cs_b); // 输出"comparable" UncompStruct ucs_a, ucs_b; compare(ucs_a, ucs_b); // 输出"not comparable" bad_type bt_a; bad_type bt_b; compare(bt_a, bt_b); // 输出"not comparable",编译通过 }
关键说明
ActuallyComparable概念直接检查a == b、a < b、a > b表达式是否合法可调用,且返回值可转换为bool,比std::three_way_comparable更贴近实际可比较需求。- 对于Boost.MultiIndex这类模板化比较运算符的类型,
std::three_way_comparable会误判其满足概念,但ActuallyComparable会在表达式检查阶段发现内部元素不可比较,从而正确判定类型不可比较。 - 若仅需检测相等性或部分比较操作,可调整
ActuallyComparable中的requires表达式,比如只保留{ a == b } -> std::convertible_to<bool>来检测相等比较能力。
内容的提问来源于stack exchange,提问作者uni
相关产品推荐
相关产品推荐

