繁体   English   中英

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

[英]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;
}

我有很多这样的类和免费的函数运算符。

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

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

我在想——通过元编程——我可以让存档知道我正在序列化 MyClass。 然后它可以做这样的检查:

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

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

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

有什么绝妙的想法吗? 谢谢!

对我来说,这仅在常量来自重载时才有效,并且您无法在不更新重载而不是某些类方法的情况下消除错误。

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

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

由于您的最终目标是强制序列化函数和类定义之间的一致性,您不妨考虑自动生成序列化方法。 这将比接受的答案更复杂,但从长远来看可能会为您节省一些时间。 我知道两种方法都需要一些讨厌的技巧:一种依赖于这里描述的包装类http://cplusplus.bordoon.com/dark_side.html ,另一种是使用 xmacro 技术https://en.wikipedia.org /wiki/X_Macro最终能够做这样的事情 -> 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

现在我需要想出一个更好的信息。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM