如何对(任意)POD C++ 结构施加词典顺序?

How to impose a lexicographic order on an (arbitrary) POD C++ struct?

我有一些 POD struct foo;假设它是 struct foo { int x; unsigned y; }。我希望能够比较 struct foo 的使用词典顺序 - 当然是按其字段的顺序。也就是说,我希望所有运算符 <、==、> 等都适用于 struct foo

我能否以某种通用的方式做到这一点,而不用任何 reflection voodoo 装饰我的结构定义 - 并且不只是拼写出所有这些运算符定义?还是能够做到这一点 "language reflection dependent" 期望值过高?

Can I do this in some generic way, without having decorated my structure definition with any reflection voodoo - and without just spelling out all those operator definitions?

不,目前的 c++ 标准无法以通用方式实现这一点。

我什至不知道你说的 "reflection voodo" 是什么意思,因为标准还不支持类型反射。

即使将来是这样,我怀疑像 按字典顺序列出 这样的操作是否会首先可用。


Or is the ability to do this too much of a "language reflection dependent" expectation?

可能是的。您可以尝试使用像 c# 这样具有反射的语言,但提供通用运算符实现仍然很棘手。

目前没有类似

的快捷方式
auto operator < (const foo &a, const foo &b) {
    return std::tie(a.x, a.y) < std::tie(b.x, b.y);
}

在标准 C++ 中(以及在 Boost afaics 中)。

由于这确实是不必要且容易出错的类型,Defaulted comparison operators 已被提议,但尚未添加到标准 C++(截至 C++17 的当前草案)。

你不能在标准 C++11 或 C++14 中这样做。

您可以考虑让一些程序或脚本生成相关的 struct-s 及其比较函数。也许使用像 GPP or m4 (or write your own C++ generator). Qt moc 这样的外部预处理器可能会鼓舞人心。

或者您可以考虑使用一些编译器插件(如果使用 GCC: coded in C++, or in MELT; if using Clang: coded in C++)来帮助完成这项工作。这可能需要数周的工作(因为 C++ 编译器是非常复杂的野兽),因此仅对于大型程序是值得的。

您可以在 C++1z 中执行此操作。根据 的回答,我准备了以下概念证明:

struct anything {
    template<class T> operator T()const;
};

namespace details {
template<class T, class Is, class=void>
struct can_construct_with_N:std::false_type {};

template<class T, std::size_t...Is>
struct can_construct_with_N<T, std::index_sequence<Is...>,
        std::void_t< decltype(T{(void(Is),anything{})...}) >>:
                                                             std::true_type
{};
}

template<class T, std::size_t N>
using can_construct_with_N=details::can_construct_with_N<T, std::make_index_sequence<N>>;

namespace details {
template<std::size_t Min, std::size_t Range, template<std::size_t N>class target>
struct maximize: std::conditional_t<
    maximize<Min, Range/2, target>{} == (Min+Range/2)-1,
    maximize<Min+Range/2, (Range+1)/2, target>,
    maximize<Min, Range/2, target>
>{};

template<std::size_t Min, template<std::size_t N>class target>
struct maximize<Min, 1, target>: std::conditional_t<
    target<Min>{},
    std::integral_constant<std::size_t,Min>,
    std::integral_constant<std::size_t,Min-1>
>{};

template<std::size_t Min, template<std::size_t N>class target>
struct maximize<Min, 0, target>:
    std::integral_constant<std::size_t,Min-1>
{};

template<class T>
struct construct_searcher {
    template<std::size_t N>
    using result = ::can_construct_with_N<T, N>;
};

template<class T, std::size_t Cap=4>
using construct_arity = details::maximize< 0, Cap, details::construct_searcher<T>::template result >;

template<typename T>
constexpr auto tie_as_tuple_impl(std::integral_constant<size_t, 1>, T&& t){
    auto&& [a] = t;
    return std::tie(a);
}

template<typename T>
constexpr auto tie_as_tuple_impl(std::integral_constant<size_t, 2>, T&& t){
    auto&& [a,b] = t;
    return std::tie(a,b);
}

template<typename T>
constexpr auto tie_as_tuple_impl(std::integral_constant<size_t, 3>, T&& t){
    auto&& [a,b,c] = t;
    return std::tie(a,b,c);
}

template<size_t S, typename T>
constexpr auto tie_as_tuple(T&& t){
    return tie_as_tuple_impl(std::integral_constant<size_t, S>{}, std::forward<T>(t));
}

}

template<typename T>
constexpr auto tie_as_tuple(T&& t){
    constexpr size_t S = details::construct_arity<std::decay_t<T>>::value;
    return details::tie_as_tuple<S>(std::forward<T>(t));
}

现在,您可以使用 tie_as_tuple 创建一个元组,其中已按照您要求的方式定义了您要求的所有运算符。

demo

请注意,我必须准备几个 tie_as_tuple_impl 的重载,每个重载对应一个结构中的每个元素数量,但它会随着结构元素的数量线性增长。


在 C++14 中,magic_get that could allow similar solution, but it has its caveats, see here 提供了更多信息。

从 C++20 开始,只需将默认的 spaceship-operator 添加到 class、

struct foo
{
    //...

    auto operator<=>(foo const&) = default;
};

我想现在任何有兴趣的人都知道了,但它仍然可以作为问题的答案。