如何在(任意)POD c++结构体上施加字典顺序



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

我可以用一种通用的方式做到这一点,而不需要用任何反射来修饰我的结构定义,也不需要拼写出所有的操作符定义吗?还是说这种能力过于依赖于"语言反射"?

您可以在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来创建一个元组,该元组已经定义了您所要求的所有操作符。

演示

请注意,我必须准备几个tie_as_tuple_impl的重载,每个重载一个结构体中的元素数量,但这对于结构体元素的数量是线性扩展的。


在c++ 14中,magic_get可以允许类似的解决方案,但它有其注意事项,请参阅此处了解更多信息。

我可以用一种通用的方式做到这一点,而不需要用任何反射来修饰我的结构定义,也不需要拼写出所有的操作符定义吗?

不,在当前的c++标准中没有办法以通用的方式实现这一点。

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

即使将来是这样,我也怀疑像list这样按字典顺序排列的操作是否会在第一位置可用。


或者这样做的能力过于依赖于"语言反射"的期望?

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

目前没有类似

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

由于这确实是不必要且容易出错的类型,已经提出了默认比较操作符,但尚未添加到标准c++中(截至c++ 17的当前草案)。

从c++ 20开始,只需在类

中添加一个默认的太空船操作符就可以完成。
struct foo
{
    //...
    auto operator<=>(foo const&) = default;
};

我想每个感兴趣的人现在都知道了,但是它可能对这个问题的答案很有用。

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

您可以考虑使用一些程序或脚本生成相关的struct -s和它们的比较函数。也许使用一些外部预处理器,如GPP或m4(或编写自己的c++生成器)。Qt moc可能是鼓舞人心的。

或者你可以考虑使用一些编译器插件(如果使用GCC:用c++编码,或在MELT;如果使用Clang: c++编码)来帮助工作。这可能需要几个星期的工作(因为c++编译器非常复杂),所以只适合大型程序。

相关内容

  • 没有找到相关文章

最新更新