diff options
-rw-r--r-- | src/serialize.h | 32 | ||||
-rw-r--r-- | src/test/serialize_tests.cpp | 79 |
2 files changed, 109 insertions, 2 deletions
diff --git a/src/serialize.h b/src/serialize.h index 8ad5ad5147..8bca4d8ada 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -1108,10 +1108,22 @@ template <typename SubStream, typename Params> class ParamsStream { const Params& m_params; - SubStream& m_substream; + // If ParamsStream constructor is passed an lvalue argument, Substream will + // be a reference type, and m_substream will reference that argument. + // Otherwise m_substream will be a substream instance and move from the + // argument. Letting ParamsStream contain a substream instance instead of + // just a reference is useful to make the ParamsStream object self contained + // and let it do cleanup when destroyed, for example by closing files if + // SubStream is a file stream. + SubStream m_substream; public: - ParamsStream(SubStream& substream LIFETIMEBOUND, const Params& params LIFETIMEBOUND) : m_params{params}, m_substream{substream} {} + ParamsStream(SubStream&& substream, const Params& params LIFETIMEBOUND) : m_params{params}, m_substream{std::forward<SubStream>(substream)} {} + + template <typename NestedSubstream, typename Params1, typename Params2, typename... NestedParams> + ParamsStream(NestedSubstream&& s, const Params1& params1 LIFETIMEBOUND, const Params2& params2 LIFETIMEBOUND, const NestedParams&... params LIFETIMEBOUND) + : ParamsStream{::ParamsStream{std::forward<NestedSubstream>(s), params2, params...}, params1} {} + template <typename U> ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; } template <typename U> ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; } void write(Span<const std::byte> src) { m_substream.write(src); } @@ -1132,6 +1144,22 @@ public: } }; +/** + * Explicit template deduction guide is required for single-parameter + * constructor so Substream&& is treated as a forwarding reference, and + * SubStream is deduced as reference type for lvalue arguments. + */ +template <typename Substream, typename Params> +ParamsStream(Substream&&, const Params&) -> ParamsStream<Substream, Params>; + +/** + * Template deduction guide for multiple params arguments that creates a nested + * ParamsStream. + */ +template <typename Substream, typename Params1, typename Params2, typename... Params> +ParamsStream(Substream&& s, const Params1& params1, const Params2& params2, const Params&... params) -> + ParamsStream<decltype(ParamsStream{std::forward<Substream>(s), params2, params...}), Params1>; + /** Wrapper that serializes objects with the specified parameters. */ template <typename Params, typename T> class ParamsWrapper diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index 18356f2df0..ad4852d13d 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -15,6 +15,18 @@ BOOST_FIXTURE_TEST_SUITE(serialize_tests, BasicTestingSetup) +// For testing move-semantics, declare a version of datastream that can be moved +// but is not copyable. +class UncopyableStream : public DataStream +{ +public: + using DataStream::DataStream; + UncopyableStream(const UncopyableStream&) = delete; + UncopyableStream& operator=(const UncopyableStream&) = delete; + UncopyableStream(UncopyableStream&&) noexcept = default; + UncopyableStream& operator=(UncopyableStream&&) noexcept = default; +}; + class CSerializeMethodsTestSingle { protected: @@ -344,6 +356,73 @@ public: } }; +struct OtherParam { + uint8_t param; + SER_PARAMS_OPFUNC +}; + +//! Checker for value of OtherParam. When being serialized, serializes the +//! param to the stream. When being unserialized, verifies the value in the +//! stream matches the param. +class OtherParamChecker +{ +public: + template <typename Stream> + void Serialize(Stream& s) const + { + const uint8_t param = s.template GetParams<OtherParam>().param; + s << param; + } + + template <typename Stream> + void Unserialize(Stream& s) const + { + const uint8_t param = s.template GetParams<OtherParam>().param; + uint8_t value; + s >> value; + BOOST_CHECK_EQUAL(value, param); + } +}; + +//! Test creating a stream with multiple parameters and making sure +//! serialization code requiring different parameters can retrieve them. Also +//! test that earlier parameters take precedence if the same parameter type is +//! specified twice. (Choice of whether earlier or later values take precedence +//! or multiple values of the same type are allowed was arbitrary, and just +//! decided based on what would require smallest amount of ugly C++ template +//! code. Intent of the test is to just ensure there is no unexpected behavior.) +BOOST_AUTO_TEST_CASE(with_params_multi) +{ + const OtherParam other_param_used{.param = 0x10}; + const OtherParam other_param_ignored{.param = 0x11}; + const OtherParam other_param_override{.param = 0x12}; + const OtherParamChecker check; + DataStream stream; + ParamsStream pstream{stream, RAW, other_param_used, other_param_ignored}; + + Base base1{0x20}; + pstream << base1 << check << other_param_override(check); + BOOST_CHECK_EQUAL(stream.str(), "\x20\x10\x12"); + + Base base2; + pstream >> base2 >> check >> other_param_override(check); + BOOST_CHECK_EQUAL(base2.m_base_data, 0x20); +} + +//! Test creating a ParamsStream that moves from a stream argument. +BOOST_AUTO_TEST_CASE(with_params_move) +{ + UncopyableStream stream{}; + ParamsStream pstream{std::move(stream), RAW, HEX, RAW}; + + Base base1{0x20}; + pstream << base1; + + Base base2; + pstream >> base2; + BOOST_CHECK_EQUAL(base2.m_base_data, 0x20); +} + BOOST_AUTO_TEST_CASE(with_params_base) { Base b{0x0F}; |