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

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

本文关键字:两个 不相关 链接 C++ 何使用 编程 函数 自由      更新时间:2023-10-16

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

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

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

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

但是如何做到这一点呢? 如果我让 Archive 期望一个值为 MyClass 的模板参数,那么运算符中的每一行都必须更改<<因为每个 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

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