aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Ofsky <ryan@ofsky.org>2023-11-22 17:56:04 -0500
committerRyan Ofsky <ryan@ofsky.org>2024-02-21 07:07:50 -0500
commite6794e475c84d9edca4a2876e2342cbb1d85f804 (patch)
tree26f39ab177e84f59da3ef1f1be516d319a691118
parentcb28849a88339c1e7ba03ffc7e38998339074e6e (diff)
serialization: Accept multiple parameters in ParamsStream constructor
Before this change it was possible but awkward to create ParamStream streams with multiple parameter objects. After this change it is straightforward. The change to support multiple parameters is implemented by letting ParamsStream contain substream instances, instead of just references to external substreams. So a side-effect of this change is that ParamStream can now accept rvalue stream arguments and be easier to use in some other cases. A test for rvalues is added in this commit, and some simplifications to non-test code are made in the next commit.
-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};