【问题标题】:c++ auto-comparator for structs用于结构的 c++ 自动比较器
【发布时间】:2017-06-18 08:52:16
【问题描述】:

有许多原始结构(数百个),用于在两个组件(例如播放器和服务器)之间传输数据。其中没有方法,只有原始数据。 任务是编写所有请求和答案,以便能够在没有服务器的情况下重播玩家场景(我们记住所有问题和所有答案,它们都是纯函数)。 所以任务是把这个结构放在没有比较器的映射中。现在我们使用memcmp,它允许不用考虑这个结构的变化,它很紧凑,但是填充等问题太多了。

是否有可能在 c++ 中通过元编程获得像 getHashValue 或任何默认比较器这样的东西? 条件: 1)我不想为每个结构创建一个比较器。 2) 如果一个字段被添加或删除,如果它破坏了现有的行为并且需要修复,我想有一个错误。 3) 我不想用结构定义更改头文件。

结构示例

struct A {
  int a;
  int b;
  c c;
}

bool operator<(const A& a1, const A& a2)
{
  if (a1.a != a2.a) return a1.a < a2.a;
  if (a1.b != a2.b) return a1.b < a2.b;
  if (a1.c != a2.c) return a1.c < a2.c;
  return false;
} 

我可以考虑用其他语言来实现这个确切的部分(收集问题/答案),如果它不需要再次描述该语言上的所有这些结构。

【问题讨论】:

  • 我猜你的意思是memcmp 而不是memcpy

标签: c++ c++11 struct comparison metaprogramming


【解决方案1】:

在 C++17 中,如果您愿意 (A) 硬编码每个结构中某处的元素数量,并且 (B) 为结构中的每个元素数量编写或生成代码,则可以实现这一点.

template<std::size_t N>
using size_k = std::integral_constant<std::size_t, N>;

template<class T>
auto auto_tie( size_k<0>, T&& t ) {
  return std::tie();
}
template<class T>
auto auto_tie( size_k<1>, T&& t ) {
  auto& [ x0 ] = std::forward<T>(t);
  return std::tie( x0 );
}
template<class T>
auto auto_tie( size_k<2>, T&& t ) {
  auto& [ x0, x1 ] = std::forward<T>(t);
  return std::tie( x0, x1 );
}
// etc

现在,在相关结构的命名空间中,写入

struct foo {
  int x;
};
struct bar {
  int a, b;
};
size_k<1> elems( foo const& ) { return {}; }
size_k<2> elems( bar const& ) { return {}; }

一个 elems 函数返回 size_k 计算元素的数量。

现在在结构的命名空间中,写:

template<class T, class Size=decltype(elems(std::declval<T const&>()))>
bool operator<( T const& lhs, T const& rhs ) {
  return auto_tie( Size{}, lhs ) < auto_tie( Size{}, rhs );
}

你就完成了。

测试代码:

foo f0{1}, f1{2};
bar b0{1,2}, b1{-7, -3};

std::cout << (f0<f1) << (f1<f0) << (f0<f0) << "\n";
std::cout << (b0<b1) << (b1<b0) << (b0<b0) << "\n";

Live example.

更进一步需要编写 3rd 方工具或等待 C++ 的反射扩展,可能在 C++20 或 23 中。

如果你弄错了elems,我相信auto_tie 中的结构化绑定代码应该会产生错误。

【讨论】:

  • 您可以通过使用Magic Get 中的技术(在cppcon video 中描述)来消除对elems 的需求。基本上,您可以获得结构元素数量的上限,并使用可转换为任何类型的类型来尝试构造该类型,从而减少尝试构造它的元素数量,直到它起作用
  • @Justin 我相信在第 9 分钟。可能需要做一些工作才能使其生成 constexpr 返回值,但我认为没有什么棘手的。
【解决方案2】:

我想您可以根据memcmp 编写自己的比较运算符。

bool operator<(const A &lhs, const A &rhs) {
    return memcmp(&lhs, &rhs, sizeof(A)) < 0;
}

当然,为每个对象编写这些可能是一种负担,因此您可以为此编写一个模板。虽然没有一些 SFINAE 会覆盖太多的类型。

 #include <type_traits>
 #include <cstring>

 template<typename T>
 std::enable_if_t<std::is_pod_v<std::decay_t<T>      //< Check if POD
                  && !std::is_fundamental_v<std::decay_t<T>>>, //< Don't override for fundamental types like 'int'
     bool>
     operator<(const T &lhs, const T &rhs) {
     return memcmp(&lhs, &rhs, sizeof(std::decay_t<T>)) < 0;
 }

编辑:请注意,此技术要求您对结构进行零初始化。

【讨论】:

  • 如果结构被填充,memcmp 不会返回不正确的值吗?例如,请参阅this answer
  • 可能,一个人可能会关心 NAN 是否比较相等(尽管这取决于对您来说重要的是什么,-0.0 和 0.0 可能是一个问题,但同样,它可能取决于您的用例)
  • 问题是我不能对它们进行零初始化,当我们不收集这些数据时会影响性能,我的意思是在产品构建中。
  • newing后不能做memset?在这种情况下,当有填充时,上述内容将不起作用:(
【解决方案3】:

看起来最好的方法是编写一个生成器,它将为这个头文件中的所有类型生成带有 bool operator

它看起来不是一个好的解决方案,但它可以避免手写代码重复,并且支持添加/删除新的结构/字段。所以我没有找到更好的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多