如何使用模板元编程在 C++ 自由函数中 link 两个不相关的 类

How to link two unrelated classes in C++ free function using template metaprogramming

我有一个序列化流运算符作为一个免费函数,如下所示:

struct MyClass { 
    static size_t size() { return 24; } // whatever my expected size is
    X x; Y y; 
};
Archive& operator<<(Archive& ar, MyClass& c) {
    ar << c.x;
    ar << c.y;
    return ar;
}

我有很多 classes 和像这样的自由函数运算符。

我想添加一个 static_assert 编译时检查,它会自动触发以对 MyClass 进行大小检查(针对某些开发人员已将字段添加到 MyClass 而忘记序列化的情况) .它将调用 MyClass::size() 以获得预期大小并与 sizeof(MyClass).

进行比较

我不想更改所有 operator<< 定义来执行此操作。它很乏味,容易出错,并且不会实现最初的意图:自动 运行 检查而无需开发人员显式编写检查(因为这永远不会发生)。另外,序列化代码来自一个库,所以我不想更改原始代码。

我在想——通过元编程——我可以让 Archive 知道我正在序列化 MyClass。然后它可以像这样进行检查:

static_assert(sizeof(MyClass) == MyClass::size();

但是如何做到这一点?如果我让 Archive 期望一个值为 MyClass 的模板参数,那么 operator<< 中的每一行都必须更改,因为每个 ar 都是不同 class 的实例:

Archive<MyClass>& operator<<(Archive<MyClass>& ar, MyClass& c) {
    Archive<X> arX;  arX << c.x;
    Archive<Y> arY;  arY << c.y;
    return ar;
}

有什么好主意吗?谢谢!

对我来说,这仅在常量来自重载时才有效,并且如果不专门更新重载而不是某些 class 方法,则无法消除错误。

但是正如您提到的,您不能向 << 运算符添加参数。无论如何,让我们尝试一下,因为您暗示可以通过将其转换为函数来更新签名:

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

Archive& Serialize(Archive& ar, MyClass& c, ExpectedSize<24>) {
    ar << c.x;
    ar << c.y;
    return ar;
}

然后,从包罗万象的重载中调用它:

template <typename T>
Archive& operator <<(Archive ar, T&& c) {
    return Serialize(ar, c, ExpectedSize<sizeof(typename std::remove_reference<T>::type)>{});
}
// non-template overloads for basic primitives
Archive& operator <<(Archive& ar, int c) { return ar; }
Archive& operator <<(Archive& ar, const char* c) { return ar; }

现在,当您 运行 代码和 sizeof(MyClass) == 16 而不是 24 时,您会收到此错误:

error: no matching function for call to 'Serialize(Archive&, MyClass&, ExpectedSize<16>)'
...
note: candidate: 'Archive& Serialize(Archive&, MyClass&, ExpectedSize<24>)'

演示:https://godbolt.org/z/sBFtBR

如果您真的想要更具体的错误消息,您可以添加一个模板来捕获丢失的重载:

template <typename T, size_t N>
Archive& Serialize(Archive& ar, T&& c, ExpectedSize<N>) {
    static_assert(sizeof(typename std::remove_reference<T>::type) != N, "Serializer needs to be updated");
}

然后你的错误信息变成:

<source>: In instantiation of 'Archive& Serialize(Archive&, T&&, ExpectedSize<N>) [with T = MyClass&; long unsigned int N = 16; ExpectedSize<N> = std::integral_constant<long unsigned int, 16>]':
<source>:11:21:   required from 'Archive& operator<<(Archive, T&&) [with T = MyClass&]'
<source>:40:12:   required from here
<source>:34:67: error: static assertion failed: Serializer needs to be updated

演示:https://godbolt.org/z/wQAvGg

由于您的最终目标是强制序列化函数与 class 定义之间的一致性,您不妨考虑自动生成序列化方法。这将比接受的答案更复杂,但从长远来看可能会为您节省一些时间。我知道两种方法都需要一些讨厌的技巧:一种依赖于包装器 classes ,如此处所述 http://cplusplus.bordoon.com/dark_side.html, another is to use xmacro technique https://en.wikipedia.org/wiki/X_Macro to be able to do something like this in the end -> https://github.com/asherikov/ariles#example.

这是我想出的。基本上我使用@parktomatomi 的用户编写 Serialize 例程的想法,但现在它被一个包罗万象的人调用 template<class T> operator<<(Archive&, T& c) 这也会对尺寸进行 static_assert 检查。

struct B {
  constexpr static size_t size() { return 20; }
  int y = 200;
};

struct C {
  constexpr static size_t size() { return 10; }
  int x = 100;
  B b;
};

template<typename T>
Archive& Serialize(Archive& ar, T& c) {
  abort();  // Should never get here
}

Archive& operator <<(Archive& ar, int x) {
  std::cout << "ar << " << x << std::endl;
  return ar;
}

template <typename T>
Archive& operator <<(Archive& ar, T& c) {
  static_assert(sizeof(T) == T::size());
  return Serialize<T>(ar, c);
}

template<>
Archive& Serialize(Archive& ar, B& b) {
  std::cout << "ar<B> << " << b.y << std::endl;
  ar << b.y;
  return ar;
}
template<>
Archive& Serialize(Archive& ar, C& c) {
  std::cout << "ar<B> << " << c.x << std::endl;
  ar << c.b;
  ar << c.x;
  return ar;
};

int main(int argc, char* argv[])
{
  Archive ar;
  C c;
  ar << c;

  //std::cout << foo(2);
}

产生

a.cpp: In instantiation of âArchive& operator<<(Archive&, T&) [with T = B]â:
a.cpp:91:11:   required from here
a.cpp:77:27: error: static assertion failed
   static_assert(sizeof(T) == T::size());
                 ~~~~~~~~~~^~~~~~~~~~~~
a.cpp: In instantiation of âArchive& operator<<(Archive&, T&) [with T = C]â:
a.cpp:100:9:   required from here
a.cpp:77:27: error: static assertion failed

现在我需要想出更好的消息。