如何使用模板元编程在自由函数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<<
定义来执行此操作。 它很乏味,容易出错,并且不会实现原始意图:自动运行检查而无需开发人员显式编写检查(因为这永远不会发生(。 另外,序列化代码来自库,所以我不想更改原始代码。
我在想 - 通过元编程 - 我可以让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
现在我需要想出一个更好的信息。
- 如何使用模板元编程在自由函数C++链接两个不相关的类
- 输入两个不专门化大小的矩阵
- 从两个不同类继承的非虚拟基类的访问成员
- 如何解决两个不使用命名空间的第三方库之间的类名冲突?
- 使用转换运算符相互转换两个不同类的对象
- 两个不同类的运行时多态性
- 什么保证两个不相关的线程中的不同不相关对象没有(不可避免的)争用条件?
- r语言 - C++ 或 Rcpp:两个不带循环的向量的比较
- 两个不相交区间最小总和
- 在两个不动点表达之间转换
- 两个不完全相同的函数指针是否兼容
- 如何比较两个不同类的对象
- 是否有两个不平等大小的向量的STD ::不匹配
- 是否可以使用 OpenCV 同时获取指向视频序列中两个不同帧的指针
- 具有两个不同类的两个不同构造函数
- 如何在具有相同基类的两个不同类中获取向量
- 两个不同类的泛型函数
- 使用 STL 算法查找集合中的前两个不相邻元素
- 如何使用一个g++命令编译多个不相关的.cpp文件
- C++同时使用两个不兼容的库,有什么选择