Skip to content

Commit 02363f3

Browse files
authored
Add support for serialisation of std::bitset (#468)
* Add support for serialisation of std::bitset * Remove unused helper concept * Remove constexpr to helper function * Remove constexpr to helper function and use concept constrained auto * Add serialiser tests * Fix test values
1 parent 100a6fe commit 02363f3

8 files changed

Lines changed: 271 additions & 3 deletions

File tree

libraries/core/lib/morpheus/core/serialisation/adapters/std/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ target_sources(MorpheusCore
33
FILE_SET HEADERS
44
FILES
55
array.hpp
6+
bitset.hpp
67
deque.hpp
78
expected.hpp
89
forward_list.hpp

libraries/core/lib/morpheus/core/serialisation/adapters/std/array.hpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
namespace morpheus::serialisation::detail
99
{
1010

11-
// template <typename T, std::size_t N>
12-
// inline constexpr bool isEnabledForRangeSerialisation<std::array<T, N>> = true;
13-
1411
template <concepts::WriteSerialiser Serialiser, typename T, std::size_t N>
1512
void serialise(Serialiser& s, std::array<T, N> const& value)
1613
{
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#pragma once
2+
3+
#include "morpheus/core/serialisation/concepts/read_serialisable.hpp"
4+
#include "morpheus/core/serialisation/concepts/read_serialiser.hpp"
5+
#include "morpheus/core/serialisation/concepts/write_serialisable.hpp"
6+
#include "morpheus/core/serialisation/concepts/write_serialiser.hpp"
7+
8+
#include <bitset>
9+
#include <type_traits>
10+
11+
namespace morpheus::serialisation::detail
12+
{
13+
14+
template <concepts::WriteSerialiser Serialiser, std::size_t N>
15+
void serialise(Serialiser& serialiser, std::bitset<N> const& bits)
16+
{
17+
static constexpr std::size_t bits_per_byte = 8;
18+
static constexpr std::size_t bits_per_uint64 = sizeof(std::uint64_t) * bits_per_byte;
19+
20+
if (serialiser.writer().isTextual())
21+
{
22+
serialiser.serialise(bits.to_string());
23+
}
24+
else if (bits.size() <= bits_per_uint64)
25+
{
26+
serialiser.serialise(static_cast<std::uint64_t>(bits.to_ullong()));
27+
}
28+
else
29+
{
30+
serialiser.writer().beginSequence(bits.size());
31+
for (std::size_t i = 0; i < bits.size(); i += bits_per_uint64)
32+
{
33+
std::uint64_t block = 0;
34+
auto const maxBit = std::min<std::size_t>(64uz, bits.size() - i);
35+
for (std::size_t j = 0uz; j < maxBit; ++j)
36+
{
37+
block |= bits[i + j] ? (1uz << j) : 0;
38+
}
39+
serialiser.serialise(block);
40+
}
41+
serialiser.writer().endSequence();
42+
}
43+
}
44+
45+
template <concepts::ReadSerialiser Serialiser, std::size_t N>
46+
std::bitset<N> deserialise(Serialiser& serialiser, std::type_identity<std::bitset<N>>)
47+
{
48+
static constexpr std::size_t bits_per_byte = 8;
49+
static constexpr std::size_t bits_per_uint64 = sizeof(std::uint64_t) * bits_per_byte;
50+
51+
if (serialiser.reader().isTextual())
52+
{
53+
return std::bitset<N>{serialiser.template deserialise<std::string>()};
54+
}
55+
else if (N <= bits_per_uint64)
56+
{
57+
return std::bitset<N>{serialiser.template deserialise<std::uint64_t>()};
58+
}
59+
else
60+
{
61+
std::bitset<N> bits;
62+
[[maybe_unused]] auto const numBlocks = serialiser.reader().beginSequence();
63+
for (std::size_t i = 0; i < bits.size(); i += bits_per_uint64)
64+
{
65+
std::uint64_t block = serialiser.template deserialise<std::uint64_t>();
66+
auto const maxBit = std::min<std::size_t>(64uz, bits.size() - i);
67+
for (std::size_t j = 0uz; j < maxBit; ++j)
68+
{
69+
bits[i + j] = (block & (1uz << j)) != 0;
70+
}
71+
}
72+
serialiser.reader().endSequence();
73+
return bits;
74+
}
75+
}
76+
77+
} // namespace morpheus::serialisation::detail

libraries/core/tests/serialisation/adapters/std/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
target_sources(MorpheusCoreTests
22
PRIVATE
3+
bitset.tests.cpp
34
chrono.tests.cpp
45
expected.tests.cpp
56
indirect.tests.cpp
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#include "morpheus/core/conformance/ranges.hpp"
2+
#include "morpheus/core/serialisation/adapters/std/bitset.hpp"
3+
#include "morpheus/core/serialisation/mock/reader.hpp"
4+
#include "morpheus/core/serialisation/mock/serialisers.hpp"
5+
#include "morpheus/core/serialisation/mock/writer.hpp"
6+
#include "morpheus/core/serialisation/read_serialiser.hpp"
7+
#include "morpheus/core/serialisation/write_serialiser.hpp"
8+
9+
#include <catch2/catch_test_macros.hpp>
10+
#include <gmock/gmock.h>
11+
#include <gtest/gtest.h>
12+
13+
#include <bitset>
14+
#include <cstdint>
15+
#include <string>
16+
#include <string_view>
17+
#include <vector>
18+
19+
namespace morpheus::serialisation
20+
{
21+
22+
using namespace ::testing;
23+
using namespace std::literals::string_view_literals;
24+
25+
std::string toBitString(conf::ranges::range auto const& values)
26+
{
27+
std::string result;
28+
result.reserve(std::size(values) * 64uz);
29+
30+
for (auto v : conf::ranges::reverse_view(values))
31+
result += std::bitset<64>(v).to_string();
32+
33+
return result;
34+
}
35+
36+
TEST_CASE("Verify serialisation of std::bitset", "[morpheus.serialisation.bitset.serialise]")
37+
{
38+
GIVEN("A std::bitset holding a 64-bit value")
39+
{
40+
constexpr std::uint64_t actualValue = 0xFEDCBA9876543210;
41+
auto const expectedStringValue = toBitString(std::vector{actualValue});
42+
auto const value = std::bitset<64>(actualValue);
43+
44+
THEN("Expect the following sequence of operations on the underlying writer")
45+
{
46+
InSequence seq;
47+
MockedWriteSerialiser serialiser;
48+
EXPECT_CALL(serialiser.writer(), isTextual()).WillOnce(Return(true));
49+
EXPECT_CALL(serialiser.writer(), write(Matcher<std::string_view>(Eq(expectedStringValue)))).Times(1);
50+
51+
WHEN("Serialising the std::bitset")
52+
{
53+
serialiser.serialise(value);
54+
}
55+
}
56+
}
57+
GIVEN("A std::bitset holding a 64-bit value")
58+
{
59+
constexpr std::uint64_t actualValue = 0xFEDCBA9876543210;
60+
auto const value = std::bitset<64>(actualValue);
61+
62+
THEN("Expect the following sequence of operations on the underlying writer")
63+
{
64+
InSequence seq;
65+
MockedWriteSerialiser serialiser;
66+
EXPECT_CALL(serialiser.writer(), isTextual()).WillOnce(Return(false));
67+
EXPECT_CALL(serialiser.writer(), write(Matcher<std::uint64_t>(Eq(actualValue)))).Times(1);
68+
69+
WHEN("Serialising the std::bitset")
70+
{
71+
serialiser.serialise(value);
72+
}
73+
}
74+
}
75+
GIVEN("A std::bitset holding a value larger than 64-bit")
76+
{
77+
std::array<std::uint64_t, 4> actualValues = {
78+
0x76543210FEDCBA98,
79+
0xBA9876543210FEDC,
80+
0x3210FEDCBA987654,
81+
0xFEDCBA9876543210,
82+
};
83+
84+
auto const value = std::bitset<256>(toBitString(actualValues));
85+
86+
THEN("Expect the following sequence of operations on the underlying writer")
87+
{
88+
InSequence seq;
89+
MockedWriteSerialiser serialiser;
90+
EXPECT_CALL(serialiser.writer(), isTextual()).WillOnce(Return(false));
91+
EXPECT_CALL(serialiser.writer(), beginSequence(std::optional(value.size()))).Times(1);
92+
for (std::size_t i = 0uz; i < actualValues.size(); ++i)
93+
{
94+
EXPECT_CALL(serialiser.writer(), write(Matcher<std::uint64_t>(Eq(actualValues[i])))).Times(1);
95+
}
96+
EXPECT_CALL(serialiser.writer(), endSequence()).Times(1);
97+
98+
WHEN("Serialising the std::bitset")
99+
{
100+
serialiser.serialise(value);
101+
}
102+
}
103+
}
104+
}
105+
106+
TEST_CASE("Verify deserialisation of std::bitset", "[morpheus.serialisation.bitset.deserialise]")
107+
{
108+
GIVEN("Expected contents of a std::bitset holding a 64-bit value with a textual reader")
109+
{
110+
constexpr std::uint64_t actualValue = 0xFEDCBA9876543210;
111+
auto const expectedStringValue = toBitString(std::vector{actualValue});
112+
113+
THEN("Expect the following sequence of operations on the underlying reader")
114+
{
115+
InSequence seq;
116+
MockedReadSerialiser<true> serialiser;
117+
EXPECT_CALL(serialiser.reader(), read(An<std::string>())).WillOnce(Return(expectedStringValue));
118+
WHEN("Deserialising the std::bitset")
119+
{
120+
using BitsetType = std::bitset<64>;
121+
auto const bitset = serialiser.deserialise<BitsetType>();
122+
REQUIRE(bitset.to_string() == expectedStringValue);
123+
}
124+
}
125+
}
126+
GIVEN("Expected contents of a std::bitset holding a 64-bit value with a non-textual reader")
127+
{
128+
constexpr std::uint64_t actualValue = 0xFEDCBA9876543210;
129+
130+
THEN("Expect the following sequence of operations on the underlying reader")
131+
{
132+
InSequence seq;
133+
MockedReadSerialiser<false> serialiser;
134+
EXPECT_CALL(serialiser.reader(), read(An<std::uint64_t>())).WillOnce(Return(actualValue));
135+
WHEN("Deserialising the std::bitset")
136+
{
137+
using BitsetType = std::bitset<64>;
138+
auto const bitset = serialiser.deserialise<BitsetType>();
139+
REQUIRE(bitset.to_ullong() == actualValue);
140+
}
141+
}
142+
}
143+
GIVEN("Expected contents of a std::bitset holding a value larger than 64-bit with a non-textual reader")
144+
{
145+
std::array<std::uint64_t, 4> actualValues = {
146+
0x76543210FEDCBA98,
147+
0xBA9876543210FEDC,
148+
0x3210FEDCBA987654,
149+
0xFEDCBA9876543210,
150+
};
151+
152+
auto const value = std::bitset<256>(toBitString(actualValues));
153+
154+
THEN("Expect the following sequence of operations on the underlying reader")
155+
{
156+
InSequence seq;
157+
MockedReadSerialiser<false> serialiser;
158+
EXPECT_CALL(serialiser.reader(), beginSequence()).WillOnce(Return(std::optional(actualValues.size())));
159+
for (std::size_t i = 0uz; i < actualValues.size(); ++i)
160+
{
161+
EXPECT_CALL(serialiser.reader(), read(An<std::uint64_t>())).WillOnce(Return(actualValues[i]));
162+
}
163+
EXPECT_CALL(serialiser.reader(), endSequence()).Times(1);
164+
165+
WHEN("Deserialising the std::bitset")
166+
{
167+
using BitsetType = std::bitset<256>;
168+
auto const bitset = serialiser.deserialise<BitsetType>();
169+
REQUIRE(bitset.to_string() == toBitString(actualValues));
170+
}
171+
}
172+
}
173+
}
174+
175+
} // namespace morpheus::serialisation

libraries/core/tests/serialisation/binary_roundtrip.tests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
#include "morpheus/core/conformance/value_types.hpp"
12
#include "morpheus/core/serialisation/adapters/aggregate.hpp"
3+
#include "morpheus/core/serialisation/adapters/std/bitset.hpp"
24
#include "morpheus/core/serialisation/adapters/std/chrono.hpp"
5+
#include "morpheus/core/serialisation/adapters/std/indirect.hpp"
36
#include "morpheus/core/serialisation/adapters/std/monostate.hpp"
47
#include "morpheus/core/serialisation/adapters/std/optional.hpp"
58
#include "morpheus/core/serialisation/adapters/std/pair.hpp"
@@ -14,6 +17,7 @@
1417

1518
#include <catch2/catch_test_macros.hpp>
1619

20+
#include <bitset>
1721
#include <chrono>
1822
#include <memory>
1923
#include <optional>
@@ -42,6 +46,8 @@ TEST_CASE("Binary serialisation can roundtrip standard library types to binary a
4246
REQUIRE(roundtrip(std::chrono::years{100}) == std::chrono::years{100});
4347
REQUIRE(roundtrip(std::chrono::months{12}) == std::chrono::months{12});
4448
}
49+
REQUIRE(roundtrip(std::bitset<4>{"1101"}) == std::bitset<4>{"1101"});
50+
REQUIRE(roundtrip(conf::vt::indirect<int>{42}) == conf::vt::indirect<int>{42});
4551
REQUIRE(roundtrip(std::monostate{}) == std::monostate{});
4652
REQUIRE(roundtrip(std::optional<int>{100}) == std::optional<int>{100});
4753
REQUIRE(roundtrip(std::optional<int>{}) == std::optional<int>{});

libraries/core/tests/serialisation/json_reader.tests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#include "morpheus/core/conformance/format.hpp"
2+
#include "morpheus/core/conformance/value_types.hpp"
23
#include "morpheus/core/serialisation/adapters/aggregate.hpp"
34
#include "morpheus/core/serialisation/adapters/hex.hpp"
45
#include "morpheus/core/serialisation/adapters/std/array.hpp"
6+
#include "morpheus/core/serialisation/adapters/std/bitset.hpp"
57
#include "morpheus/core/serialisation/adapters/std/chrono.hpp"
8+
#include "morpheus/core/serialisation/adapters/std/indirect.hpp"
69
#include "morpheus/core/serialisation/adapters/std/monostate.hpp"
710
#include "morpheus/core/serialisation/adapters/std/optional.hpp"
811
#include "morpheus/core/serialisation/adapters/std/pair.hpp"
@@ -24,6 +27,7 @@
2427
#include <catch2/matchers/catch_matchers_string.hpp>
2528

2629
#include <array>
30+
#include <bitset>
2731
#include <chrono>
2832
#include <cmath>
2933
#include <compare>
@@ -571,6 +575,8 @@ TEST_CASE("Json reader can read std types from underlying text representation",
571575
REQUIRE(test::deserialise<std::chrono::years>(R"("100y")") == std::chrono::years{100});
572576
REQUIRE(test::deserialise<std::chrono::months>(R"("12m")") == std::chrono::months{12});
573577
}
578+
REQUIRE(test::deserialise<std::bitset<4>>(R"("1101")") == std::bitset<4>("1101"));
579+
REQUIRE(test::deserialise<conf::vt::indirect<int>>(R"({"value":42})") == conf::vt::indirect<int>(42));
574580
REQUIRE(test::deserialise<std::monostate>(R"({})") == std::monostate{});
575581
REQUIRE(test::deserialise<std::optional<int>>(R"(100)") == std::optional<int>{100});
576582
REQUIRE(test::deserialise<std::optional<int>>(R"(null)") == std::optional<int>{});

libraries/core/tests/serialisation/json_writer.tests.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#include "morpheus/core/conformance/format.hpp"
22
#include "morpheus/core/serialisation/adapters/aggregate.hpp"
33
#include "morpheus/core/serialisation/adapters/hex.hpp"
4+
#include "morpheus/core/serialisation/adapters/std/bitset.hpp"
45
#include "morpheus/core/serialisation/adapters/std/chrono.hpp"
6+
#include "morpheus/core/serialisation/adapters/std/indirect.hpp"
57
#include "morpheus/core/serialisation/adapters/std/monostate.hpp"
68
#include "morpheus/core/serialisation/adapters/std/optional.hpp"
79
#include "morpheus/core/serialisation/adapters/std/pair.hpp"
@@ -18,6 +20,7 @@
1820
#include <catch2/catch_test_macros.hpp>
1921
#include <catch2/catch_tostring.hpp>
2022

23+
#include <bitset>
2124
#include <charconv> // IWYU pragma: keep
2225
#include <chrono>
2326
#include <cstdint>
@@ -409,6 +412,8 @@ TEST_CASE("Json writer can write std types to underlying text representation", "
409412
REQUIRE(test::serialise(std::chrono::years{100}) == R"("100y")");
410413
REQUIRE(test::serialise(std::chrono::months{12}) == R"("12m")");
411414
}
415+
REQUIRE(test::serialise(std::bitset<4>("1101")) == R"("1101")");
416+
REQUIRE(test::serialise(conf::vt::indirect<int>(42)) == R"({"value":42})");
412417
REQUIRE(test::serialise(std::monostate{}) == R"({})");
413418
REQUIRE(test::serialise(std::optional<int>{100}) == R"(100)");
414419
REQUIRE(test::serialise(std::optional<int>{}) == R"(null)");

0 commit comments

Comments
 (0)