如何使用模板元编程在 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
由于您的最终目标是强制序列化函数与 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
现在我需要想出更好的消息。
我有一个序列化流运算符作为一个免费函数,如下所示:
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
由于您的最终目标是强制序列化函数与 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
现在我需要想出更好的消息。