简体   繁体   English

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

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

I have a serialization streaming operator as a free function like this:我有一个序列化流操作符作为这样的免费功能:

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;
}

I have lots of classes and and free function operators like this.我有很多这样的类和免费的函数运算符。

I would like to add a static_assert compile-time check that will fire automatically to do a size check on MyClass (for the case where some developer has added a field to MyClass and forgotten to serialize it).我想添加一个static_assert编译时检查,它将自动触发以对 MyClass 进行大小检查(对于某些开发人员已将字段添加到 MyClass 并忘记对其进行序列化的情况)。 It will call MyClass::size() to get the expected size and compare against sizeof(MyClass) .它将调用MyClass::size()以获取预期大小并与sizeof(MyClass)进行比较。

I don't want to change all the operator<< definitions to do this.我不想更改所有operator<<定义来执行此操作。 It's tedious, error-prone and it won't implement the original intent: to automatically run the check without having the developer to explicitly write the check (since that'll never happen).这很乏味,容易出错,并且不会实现最初的意图:自动运行检查而无需开发人员明确编写检查(因为这永远不会发生)。 Also, the serialization code is from a library, so I don't want to change the original code.另外,序列化代码来自库,所以我不想更改原始代码。

I was thinking that--with metaprogramming--I can let Archive know that I'm serializing MyClass.我在想——通过元编程——我可以让存档知道我正在序列化 MyClass。 Then it can do a check like this:然后它可以做这样的检查:

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

But how to do this?但是如何做到这一点呢? If I make Archive expect a template parameter whose value is MyClass, then every line in the operator<< will have to change since each ar will be an instance of a different class:如果我让 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;
}

Any brilliant ideas?有什么绝妙的想法吗? Thank you!谢谢!

To me, this is only effective if the constant comes from the overload, and you can't snuff the error without updating the overload specifically instead of some class method.对我来说,这仅在常量来自重载时才有效,并且您无法在不更新重载而不是某些类方法的情况下消除错误。

But as you mentioned, you can't add an argument to the << operator.但是正如您所提到的,您不能向<<运算符添加参数。 Let's try anyway, since you hinted that you can update the signature, by converting it to a function:无论如何,让我们尝试一下,因为您暗示可以通过将签名转换为函数来更新签名:

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;
}

Then, invoke that from a catch-all overload:然后,从一个包罗万象的重载中调用它:

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; }

Now, when you run the code and sizeof(MyClass) == 16 instead of 24, you get this error:现在,当您运行代码并且sizeof(MyClass) == 16而不是 24 时,您会收到此错误:

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

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

If you really wanted a more specific error message, you can add a template to catch the missing overloads:如果你真的想要更具体的错误信息,你可以添加一个模板来捕捉丢失的重载:

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");
}

Then your error message becomes:然后你的错误信息变成:

<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

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

Since your final goal is to enforce consistency between serialization functions and class definitions, you may as well consider automatic generation of serialization methods.由于您的最终目标是强制序列化函数和类定义之间的一致性,您不妨考虑自动生成序列化方法。 This would be more involved than the accepted answer, but may save you some time in long term.这将比接受的答案更复杂,但从长远来看可能会为您节省一些时间。 I know of two approaches both of which require some nasty tricks: one relies on wrapper classes as descibed here 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 .我知道两种方法都需要一些讨厌的技巧:一种依赖于这里描述的包装类http://cplusplus.bordoon.com/dark_side.html ,另一种是使用 xmacro 技术https://en.wikipedia.org /wiki/X_Macro最终能够做这样的事情 -> https://github.com/asherikov/ariles#example

Here's what I came up with.这是我想出的。 Basically I uses @parktomatomi's idea of a user-written Serialize routine, but now it gets called by a catch-all template<class T> operator<<(Archive&, T& c) that also does the static_assert for the size check.基本上我使用@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);
}

It produces它产生

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

Now I need to come up with a better message.现在我需要想出一个更好的信息。

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

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