|
6 | 6 | #ifndef BITCOIN_SERIALIZE_H
|
7 | 7 | #define BITCOIN_SERIALIZE_H
|
8 | 8 |
|
| 9 | +#include <attributes.h> |
9 | 10 | #include <compat/endian.h>
|
10 | 11 |
|
11 | 12 | #include <algorithm>
|
@@ -194,28 +195,81 @@ const Out& AsBase(const In& x)
|
194 | 195 | template<typename Stream, typename Type, typename Operation> \
|
195 | 196 | static void SerializationOps(Type& obj, Stream& s, Operation ser_action)
|
196 | 197 |
|
| 198 | +/** |
| 199 | + * Variant of FORMATTER_METHODS that supports a declared parameter type. |
| 200 | + * |
| 201 | + * If a formatter has a declared parameter type, it must be invoked directly or |
| 202 | + * indirectly with a parameter of that type. This permits making serialization |
| 203 | + * depend on run-time context in a type-safe way. |
| 204 | + * |
| 205 | + * Example use: |
| 206 | + * struct BarParameter { bool fancy; ... }; |
| 207 | + * struct Bar { ... }; |
| 208 | + * struct FooFormatter { |
| 209 | + * FORMATTER_METHODS(Bar, obj, BarParameter, param) { |
| 210 | + * if (param.fancy) { |
| 211 | + * READWRITE(VARINT(obj.value)); |
| 212 | + * } else { |
| 213 | + * READWRITE(obj.value); |
| 214 | + * } |
| 215 | + * } |
| 216 | + * }; |
| 217 | + * which would then be invoked as |
| 218 | + * READWRITE(WithParams(BarParameter{...}, Using<FooFormatter>(obj.foo))) |
| 219 | + * |
| 220 | + * WithParams(parameter, obj) can be invoked anywhere in the call stack; it is |
| 221 | + * passed down recursively into all serialization code, until another |
| 222 | + * WithParams overrides it. |
| 223 | + * |
| 224 | + * Parameters will be implicitly converted where appropriate. This means that |
| 225 | + * "parent" serialization code can use a parameter that derives from, or is |
| 226 | + * convertible to, a "child" formatter's parameter type. |
| 227 | + * |
| 228 | + * Compilation will fail in any context where serialization is invoked but |
| 229 | + * no parameter of a type convertible to BarParameter is provided. |
| 230 | + */ |
| 231 | +#define FORMATTER_METHODS_PARAMS(cls, obj, paramcls, paramobj) \ |
| 232 | + template <typename Stream> \ |
| 233 | + static void Ser(Stream& s, const cls& obj) { SerializationOps(obj, s, ActionSerialize{}, s.GetParams()); } \ |
| 234 | + template <typename Stream> \ |
| 235 | + static void Unser(Stream& s, cls& obj) { SerializationOps(obj, s, ActionUnserialize{}, s.GetParams()); } \ |
| 236 | + template <typename Stream, typename Type, typename Operation> \ |
| 237 | + static void SerializationOps(Type& obj, Stream& s, Operation ser_action, const paramcls& paramobj) |
| 238 | + |
| 239 | +#define BASE_SERIALIZE_METHODS(cls) \ |
| 240 | + template <typename Stream> \ |
| 241 | + void Serialize(Stream& s) const \ |
| 242 | + { \ |
| 243 | + static_assert(std::is_same<const cls&, decltype(*this)>::value, "Serialize type mismatch"); \ |
| 244 | + Ser(s, *this); \ |
| 245 | + } \ |
| 246 | + template <typename Stream> \ |
| 247 | + void Unserialize(Stream& s) \ |
| 248 | + { \ |
| 249 | + static_assert(std::is_same<cls&, decltype(*this)>::value, "Unserialize type mismatch"); \ |
| 250 | + Unser(s, *this); \ |
| 251 | + } |
| 252 | + |
197 | 253 | /**
|
198 | 254 | * Implement the Serialize and Unserialize methods by delegating to a single templated
|
199 | 255 | * static method that takes the to-be-(de)serialized object as a parameter. This approach
|
200 | 256 | * has the advantage that the constness of the object becomes a template parameter, and
|
201 | 257 | * thus allows a single implementation that sees the object as const for serializing
|
202 | 258 | * and non-const for deserializing, without casts.
|
203 | 259 | */
|
204 |
| -#define SERIALIZE_METHODS(cls, obj) \ |
205 |
| - template<typename Stream> \ |
206 |
| - void Serialize(Stream& s) const \ |
207 |
| - { \ |
208 |
| - static_assert(std::is_same<const cls&, decltype(*this)>::value, "Serialize type mismatch"); \ |
209 |
| - Ser(s, *this); \ |
210 |
| - } \ |
211 |
| - template<typename Stream> \ |
212 |
| - void Unserialize(Stream& s) \ |
213 |
| - { \ |
214 |
| - static_assert(std::is_same<cls&, decltype(*this)>::value, "Unserialize type mismatch"); \ |
215 |
| - Unser(s, *this); \ |
216 |
| - } \ |
| 260 | +#define SERIALIZE_METHODS(cls, obj) \ |
| 261 | + BASE_SERIALIZE_METHODS(cls) \ |
217 | 262 | FORMATTER_METHODS(cls, obj)
|
218 | 263 |
|
| 264 | +/** |
| 265 | + * Variant of SERIALIZE_METHODS that supports a declared parameter type. |
| 266 | + * |
| 267 | + * See FORMATTER_METHODS_PARAMS for more information on parameters. |
| 268 | + */ |
| 269 | +#define SERIALIZE_METHODS_PARAMS(cls, obj, paramcls, paramobj) \ |
| 270 | + BASE_SERIALIZE_METHODS(cls) \ |
| 271 | + FORMATTER_METHODS_PARAMS(cls, obj, paramcls, paramobj) |
| 272 | + |
219 | 273 | // clang-format off
|
220 | 274 | #ifndef CHAR_EQUALS_INT8
|
221 | 275 | template <typename Stream> void Serialize(Stream&, char) = delete; // char serialization forbidden. Use uint8_t or int8_t
|
@@ -1080,4 +1134,61 @@ size_t GetSerializeSizeMany(int nVersion, const T&... t)
|
1080 | 1134 | return sc.size();
|
1081 | 1135 | }
|
1082 | 1136 |
|
| 1137 | +/** Wrapper that overrides the GetParams() function of a stream (and hides GetVersion/GetType). */ |
| 1138 | +template <typename Params, typename SubStream> |
| 1139 | +class ParamsStream |
| 1140 | +{ |
| 1141 | + const Params& m_params; |
| 1142 | + SubStream& m_substream; // private to avoid leaking version/type into serialization code that shouldn't see it |
| 1143 | + |
| 1144 | +public: |
| 1145 | + ParamsStream(const Params& params LIFETIMEBOUND, SubStream& substream LIFETIMEBOUND) : m_params{params}, m_substream{substream} {} |
| 1146 | + template <typename U> ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; } |
| 1147 | + template <typename U> ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; } |
| 1148 | + void write(Span<const std::byte> src) { m_substream.write(src); } |
| 1149 | + void read(Span<std::byte> dst) { m_substream.read(dst); } |
| 1150 | + void ignore(size_t num) { m_substream.ignore(num); } |
| 1151 | + bool eof() const { return m_substream.eof(); } |
| 1152 | + size_t size() const { return m_substream.size(); } |
| 1153 | + const Params& GetParams() const { return m_params; } |
| 1154 | + int GetVersion() = delete; // Deprecated with Params usage |
| 1155 | + int GetType() = delete; // Deprecated with Params usage |
| 1156 | +}; |
| 1157 | + |
| 1158 | +/** Wrapper that serializes objects with the specified parameters. */ |
| 1159 | +template <typename Params, typename T> |
| 1160 | +class ParamsWrapper |
| 1161 | +{ |
| 1162 | + static_assert(std::is_lvalue_reference<T>::value, "ParamsWrapper needs an lvalue reference type T"); |
| 1163 | + const Params& m_params; |
| 1164 | + T m_object; |
| 1165 | + |
| 1166 | +public: |
| 1167 | + explicit ParamsWrapper(const Params& params, T obj) : m_params{params}, m_object{obj} {} |
| 1168 | + |
| 1169 | + template <typename Stream> |
| 1170 | + void Serialize(Stream& s) const |
| 1171 | + { |
| 1172 | + ParamsStream ss{m_params, s}; |
| 1173 | + ::Serialize(ss, m_object); |
| 1174 | + } |
| 1175 | + template <typename Stream> |
| 1176 | + void Unserialize(Stream& s) |
| 1177 | + { |
| 1178 | + ParamsStream ss{m_params, s}; |
| 1179 | + ::Unserialize(ss, m_object); |
| 1180 | + } |
| 1181 | +}; |
| 1182 | + |
| 1183 | +/** |
| 1184 | + * Return a wrapper around t that (de)serializes it with specified parameter params. |
| 1185 | + * |
| 1186 | + * See FORMATTER_METHODS_PARAMS for more information on serialization parameters. |
| 1187 | + */ |
| 1188 | +template <typename Params, typename T> |
| 1189 | +static auto WithParams(const Params& params, T&& t) |
| 1190 | +{ |
| 1191 | + return ParamsWrapper<Params, T&>{params, t}; |
| 1192 | +} |
| 1193 | + |
1083 | 1194 | #endif // BITCOIN_SERIALIZE_H
|
0 commit comments