Skip to content

Commit e6794e4

Browse files
committed
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.
1 parent cb28849 commit e6794e4

File tree

2 files changed

+109
-2
lines changed

2 files changed

+109
-2
lines changed

src/serialize.h

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,10 +1108,22 @@ template <typename SubStream, typename Params>
11081108
class ParamsStream
11091109
{
11101110
const Params& m_params;
1111-
SubStream& m_substream;
1111+
// If ParamsStream constructor is passed an lvalue argument, Substream will
1112+
// be a reference type, and m_substream will reference that argument.
1113+
// Otherwise m_substream will be a substream instance and move from the
1114+
// argument. Letting ParamsStream contain a substream instance instead of
1115+
// just a reference is useful to make the ParamsStream object self contained
1116+
// and let it do cleanup when destroyed, for example by closing files if
1117+
// SubStream is a file stream.
1118+
SubStream m_substream;
11121119

11131120
public:
1114-
ParamsStream(SubStream& substream LIFETIMEBOUND, const Params& params LIFETIMEBOUND) : m_params{params}, m_substream{substream} {}
1121+
ParamsStream(SubStream&& substream, const Params& params LIFETIMEBOUND) : m_params{params}, m_substream{std::forward<SubStream>(substream)} {}
1122+
1123+
template <typename NestedSubstream, typename Params1, typename Params2, typename... NestedParams>
1124+
ParamsStream(NestedSubstream&& s, const Params1& params1 LIFETIMEBOUND, const Params2& params2 LIFETIMEBOUND, const NestedParams&... params LIFETIMEBOUND)
1125+
: ParamsStream{::ParamsStream{std::forward<NestedSubstream>(s), params2, params...}, params1} {}
1126+
11151127
template <typename U> ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; }
11161128
template <typename U> ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; }
11171129
void write(Span<const std::byte> src) { m_substream.write(src); }
@@ -1132,6 +1144,22 @@ class ParamsStream
11321144
}
11331145
};
11341146

1147+
/**
1148+
* Explicit template deduction guide is required for single-parameter
1149+
* constructor so Substream&& is treated as a forwarding reference, and
1150+
* SubStream is deduced as reference type for lvalue arguments.
1151+
*/
1152+
template <typename Substream, typename Params>
1153+
ParamsStream(Substream&&, const Params&) -> ParamsStream<Substream, Params>;
1154+
1155+
/**
1156+
* Template deduction guide for multiple params arguments that creates a nested
1157+
* ParamsStream.
1158+
*/
1159+
template <typename Substream, typename Params1, typename Params2, typename... Params>
1160+
ParamsStream(Substream&& s, const Params1& params1, const Params2& params2, const Params&... params) ->
1161+
ParamsStream<decltype(ParamsStream{std::forward<Substream>(s), params2, params...}), Params1>;
1162+
11351163
/** Wrapper that serializes objects with the specified parameters. */
11361164
template <typename Params, typename T>
11371165
class ParamsWrapper

src/test/serialize_tests.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@
1515

1616
BOOST_FIXTURE_TEST_SUITE(serialize_tests, BasicTestingSetup)
1717

18+
// For testing move-semantics, declare a version of datastream that can be moved
19+
// but is not copyable.
20+
class UncopyableStream : public DataStream
21+
{
22+
public:
23+
using DataStream::DataStream;
24+
UncopyableStream(const UncopyableStream&) = delete;
25+
UncopyableStream& operator=(const UncopyableStream&) = delete;
26+
UncopyableStream(UncopyableStream&&) noexcept = default;
27+
UncopyableStream& operator=(UncopyableStream&&) noexcept = default;
28+
};
29+
1830
class CSerializeMethodsTestSingle
1931
{
2032
protected:
@@ -344,6 +356,73 @@ class Derived : public Base
344356
}
345357
};
346358

359+
struct OtherParam {
360+
uint8_t param;
361+
SER_PARAMS_OPFUNC
362+
};
363+
364+
//! Checker for value of OtherParam. When being serialized, serializes the
365+
//! param to the stream. When being unserialized, verifies the value in the
366+
//! stream matches the param.
367+
class OtherParamChecker
368+
{
369+
public:
370+
template <typename Stream>
371+
void Serialize(Stream& s) const
372+
{
373+
const uint8_t param = s.template GetParams<OtherParam>().param;
374+
s << param;
375+
}
376+
377+
template <typename Stream>
378+
void Unserialize(Stream& s) const
379+
{
380+
const uint8_t param = s.template GetParams<OtherParam>().param;
381+
uint8_t value;
382+
s >> value;
383+
BOOST_CHECK_EQUAL(value, param);
384+
}
385+
};
386+
387+
//! Test creating a stream with multiple parameters and making sure
388+
//! serialization code requiring different parameters can retrieve them. Also
389+
//! test that earlier parameters take precedence if the same parameter type is
390+
//! specified twice. (Choice of whether earlier or later values take precedence
391+
//! or multiple values of the same type are allowed was arbitrary, and just
392+
//! decided based on what would require smallest amount of ugly C++ template
393+
//! code. Intent of the test is to just ensure there is no unexpected behavior.)
394+
BOOST_AUTO_TEST_CASE(with_params_multi)
395+
{
396+
const OtherParam other_param_used{.param = 0x10};
397+
const OtherParam other_param_ignored{.param = 0x11};
398+
const OtherParam other_param_override{.param = 0x12};
399+
const OtherParamChecker check;
400+
DataStream stream;
401+
ParamsStream pstream{stream, RAW, other_param_used, other_param_ignored};
402+
403+
Base base1{0x20};
404+
pstream << base1 << check << other_param_override(check);
405+
BOOST_CHECK_EQUAL(stream.str(), "\x20\x10\x12");
406+
407+
Base base2;
408+
pstream >> base2 >> check >> other_param_override(check);
409+
BOOST_CHECK_EQUAL(base2.m_base_data, 0x20);
410+
}
411+
412+
//! Test creating a ParamsStream that moves from a stream argument.
413+
BOOST_AUTO_TEST_CASE(with_params_move)
414+
{
415+
UncopyableStream stream{};
416+
ParamsStream pstream{std::move(stream), RAW, HEX, RAW};
417+
418+
Base base1{0x20};
419+
pstream << base1;
420+
421+
Base base2;
422+
pstream >> base2;
423+
BOOST_CHECK_EQUAL(base2.m_base_data, 0x20);
424+
}
425+
347426
BOOST_AUTO_TEST_CASE(with_params_base)
348427
{
349428
Base b{0x0F};

0 commit comments

Comments
 (0)