aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/serialize.h32
-rw-r--r--src/test/serialize_tests.cpp79
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};