递归数据结构中默认三路比较运算符的编译问题及解决方案问询
我尝试在递归数据结构中使用默认的三路比较运算符,代码如下:
struct MyStruct { int i; std::vector<MyStruct> v; std::partial_ordering operator<=>(const MyStruct &a) const = default; }; // 失败:无法推导比较结果类型 static_assert(std::same_as<std::compare_three_way_result_t<MyStruct>, std::partial_ordering>);
这里存在循环依赖:默认的<=>运算符依赖整数比较和std::vector的比较类别,而vector的比较类别又依赖其value_type(即MyStruct)的比较类别。即使显式声明比较类型为std::partial_ordering也无济于事,但手动实现该函数却能正常工作:
struct MyStruct { int i; std::vector<MyStruct> v; std::partial_ordering operator<=>(const MyStruct &other) const { if (auto r = i <=> other.i; r != 0) return r; return v <=> other.v; } }; // 正常 static_assert(std::same_as<std::compare_three_way_result_t<MyStruct>, std::partial_ordering>);
我的问题:
- 是否有办法绕过这个问题?规范禁止重载
std::compare_three_way_result,那有没有其他方式影响vector或std::lexicographical_compare_three_way? - 能否使用concepts或requires子句检测不完整类型?作为替代方案,我可自定义类在类型不完整时默认使用
std::partial_ordering。
问题解答
1. 绕过默认<=>的循环依赖问题
没有直接的标准方法能绕过这个默认运算符的循环依赖限制,原因如下:
- 默认生成的
<=>会立即推导所有成员的比较结果类型,包括std::vector<MyStruct>的比较类型,而此时MyStruct的比较类型尚未完全确定,形成循环依赖。 - 手动实现的
<=>之所以能工作,是因为函数体的类型推导是延迟到实例化阶段的:当执行v <=> other.v时,MyStruct已经是完整类型,std::vector的比较类型可以正常推导。
你无法直接修改std::vector或std::lexicographical_compare_three_way的行为——标准不允许在std命名空间中添加非标准允许的特化。唯一可行的替代方案就是保持手动实现<=>,或者调整数据结构设计(比如将std::vector替换为指针/智能指针,推迟比较的类型推导,但这会改变语义)。
2. 用concepts检测不完整类型
可以通过自定义concept来检测类型是否完整,核心是利用C++中“对不完整类型应用sizeof会触发编译错误”的规则,结合requires子句的SFINAE特性:
#include <type_traits> template<typename T> concept complete_type = requires { sizeof(T); };
当T是完整类型时,sizeof(T)合法,complete_type<T>为true;若T不完整,则sizeof(T)不合法,concept约束不满足。
需要注意的是,这个检测仅在模板上下文中生效,因为SFINAE只对模板参数替换的错误起作用。对于非模板场景,直接对不完整类型使用sizeof会直接触发编译错误,无法通过concept优雅处理。
如果要实现“类型不完整时默认使用std::partial_ordering”的自定义类,可以结合该concept和条件推导:
#include <compare> #include <vector> template<typename T> concept complete_type = requires { sizeof(T); }; template<typename T> struct DefaultCompare { using type = std::partial_ordering; }; template<complete_type T> struct DefaultCompare<T> { using type = decltype(std::declval<const T&>() <=> std::declval<const T&>()); }; // 使用示例 struct MyStruct; using CompareType = DefaultCompare<MyStruct>::type; // 此时MyStruct不完整,得到std::partial_ordering struct MyStruct { int i; std::vector<MyStruct> v; typename DefaultCompare<MyStruct>::type operator<=>(const MyStruct& other) const = default; };
不过这个方案对你的原始场景帮助有限——因为默认<=>的生成依然会遇到循环依赖问题,最终还是需要手动实现运算符才能解决递归类型的比较问题。
内容的提问来源于stack exchange,提问作者user3188445

