Skip to content

Commit 2c741b4

Browse files
Change Packer and Unpacker to support any byte type (#3) (#5)
* feat: Change Packer and Unpacker to support any byte type * refactor: Rename concepts for consistency and clarity * refactor: Update unpacking functions to use std::byte for improved type safety * refactor: Simplify Packer and Unpacker usage by removing unnecessary converting iterator * refactor: Specify template parameters in pack function for clarity * refactor: Improve byte conversion in current function and add byte type tests * refactor: Extend byte_type concept to include std::int8_t and add nested object packing tests * refactor: Simplify unpacking calls by removing explicit byte type parameters * refactor: Include <cstring> header for improved string manipulation * refactor: Adjust indentation in byte_type_tests for improved readability * refactor: Include <ranges> header and add constraint for Packer template * refactor: Update unpack template parameters for improved type deduction * refactor: Remove unnecessary reinterpret_cast for improved clarity * refactor: Simplify type casting in check_constant method for clarity * refactor: Consolidate byte type tests into a templated test suite for better maintainability * refactor: Replace custom span_value_type with std::ranges::range_value_t for improved clarity and consistency --------- Co-authored-by: Rene Windegger <rene@windegger.wtf>
1 parent 88a300f commit 2c741b4

7 files changed

Lines changed: 198 additions & 113 deletions

File tree

examples/first_example.cpp

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,21 @@
22
// Created by Rene Windegger on 28/10/2025.
33
//
44
#include <iostream>
5-
#include <cstddef>
65
#include <iterator>
76
#include <utility>
87
#include <vector>
98
#include <map>
109
#include <msgpack23/msgpack23.h>
1110

12-
class converting_insert_iterator final {
13-
public:
14-
using difference_type = std::ptrdiff_t;
15-
16-
constexpr explicit converting_insert_iterator(std::back_insert_iterator<std::vector<unsigned char>> &&iterator) : store_{std::move(iterator)} {}
17-
18-
constexpr converting_insert_iterator &operator=(const std::byte &value) {
19-
store_ = std::to_underlying(value);
20-
return *this;
21-
}
22-
23-
constexpr converting_insert_iterator &operator=(std::byte &&value) {
24-
store_ = std::to_underlying(value);
25-
return *this;
26-
}
27-
28-
[[nodiscard]] constexpr converting_insert_iterator &operator*() {
29-
return *this;
30-
}
31-
32-
constexpr converting_insert_iterator &operator++() {
33-
return *this;
34-
}
35-
36-
constexpr converting_insert_iterator operator++(int) {
37-
return *this;
38-
}
39-
private:
40-
std::back_insert_iterator<std::vector<unsigned char>> store_;
41-
};
42-
4311
int main() {
4412
std::map<std::string, int> const original {{"apple", 1}, {"banana", 2}};
4513

4614
std::vector<unsigned char> data{};
47-
msgpack23::Packer packer { converting_insert_iterator{ std::back_inserter(data) } };
15+
msgpack23::Packer packer { std::back_inserter(data) };
4816
packer(original);
4917

5018
std::map<std::string, int> unpacked;
51-
msgpack23::Unpacker unpacker {
52-
std::span<std::byte>{
53-
reinterpret_cast<std::byte *>(data.data()),
54-
data.size()
55-
}
56-
};
19+
msgpack23::Unpacker unpacker { data };
5720
unpacker(unpacked);
5821

5922
for (auto const& [key, value] : unpacked) {

examples/second_example.cpp

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,10 @@
22
// Created by Rene Windegger on 28/10/2025.
33
//
44
#include <iostream>
5-
#include <cstddef>
65
#include <iterator>
7-
#include <utility>
86
#include <vector>
9-
#include <map>
107
#include <msgpack23/msgpack23.h>
118

12-
class converting_insert_iterator final {
13-
public:
14-
using difference_type = std::ptrdiff_t;
15-
16-
constexpr explicit converting_insert_iterator(std::back_insert_iterator<std::vector<unsigned char>> &&iterator) : store_{std::move(iterator)} {}
17-
18-
constexpr converting_insert_iterator &operator=(const std::byte &value) {
19-
store_ = std::to_underlying(value);
20-
return *this;
21-
}
22-
23-
constexpr converting_insert_iterator &operator=(std::byte &&value) {
24-
store_ = std::to_underlying(value);
25-
return *this;
26-
}
27-
28-
[[nodiscard]] constexpr converting_insert_iterator &operator*() {
29-
return *this;
30-
}
31-
32-
constexpr converting_insert_iterator &operator++() {
33-
return *this;
34-
}
35-
36-
constexpr converting_insert_iterator operator++(int) {
37-
return *this;
38-
}
39-
private:
40-
std::back_insert_iterator<std::vector<unsigned char>> store_;
41-
};
42-
439
struct MyData {
4410
std::int64_t my_integer;
4511
std::string my_string;
@@ -59,12 +25,9 @@ int main() {
5925
MyData const original {42, "Hello"};
6026

6127
std::vector<unsigned char> data{};
62-
msgpack23::pack(converting_insert_iterator{ std::back_inserter(data) }, original);
28+
msgpack23::pack(std::back_inserter(data), original);
6329

64-
const auto [my_integer, my_string] = msgpack23::unpack<MyData>(std::span{
65-
reinterpret_cast<std::byte *>(data.data()),
66-
data.size()
67-
});
30+
const auto [my_integer, my_string] = msgpack23::unpack<MyData>(data);
6831

6932
std::cout << my_integer << ' ' << my_string << '\n';
7033
}

include/msgpack23/msgpack23.h

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <cstddef>
1111
#include <cstring>
1212
#include <iterator>
13+
#include <ranges>
1314
#include <span>
1415
#include <string>
1516
#include <type_traits>
@@ -156,11 +157,19 @@ namespace msgpack23 {
156157
constexpr counting_inserter operator++(int) {
157158
return *this;
158159
}
160+
159161
private:
160162
std::size_t *size_{};
161163
};
162164

163-
template<std::output_iterator<std::byte> Iter>
165+
template<typename T>
166+
concept byte_type = std::same_as<T, std::byte>
167+
or std::same_as<T, char>
168+
or std::same_as<T, unsigned char>
169+
or std::same_as<T, std::uint8_t>
170+
or std::same_as<T, std::int8_t>;
171+
172+
template<byte_type B, std::output_iterator<B> Iter>
164173
class Packer final {
165174
public:
166175
template<typename... Types>
@@ -175,13 +184,13 @@ namespace msgpack23 {
175184
Iter store_;
176185

177186
void emplace_constant(FormatConstants const &value) {
178-
*store_++ = static_cast<std::byte>(std::to_underlying(value));
187+
*store_++ = static_cast<B>(std::to_underlying(value));
179188
}
180189

181190
template<std::integral T>
182191
void emplace_integral(T const &value) {
183192
auto const serialize_value = to_big_endian(value);
184-
auto const bytes = std::bit_cast<std::array<std::byte, sizeof(serialize_value)> >(serialize_value);
193+
auto const bytes = std::bit_cast<std::array<B, sizeof(serialize_value)> >(serialize_value);
185194
std::copy(bytes.begin(), bytes.end(), store_);
186195
}
187196

@@ -193,8 +202,8 @@ namespace msgpack23 {
193202

194203
[[nodiscard]] bool pack_map_header(std::size_t const n) {
195204
if (n < 16) {
196-
constexpr auto size_mask = static_cast<std::byte>(0b10000000);
197-
*store_++ = static_cast<std::byte>(n) | size_mask;
205+
constexpr auto size_mask = static_cast<B>(0b10000000);
206+
*store_++ = static_cast<B>(n) | size_mask;
198207
} else if (n < std::numeric_limits<std::uint16_t>::max()) {
199208
emplace_combined(FormatConstants::map16, static_cast<std::uint16_t>(n));
200209
} else if (n < std::numeric_limits<std::uint32_t>::max()) {
@@ -207,8 +216,8 @@ namespace msgpack23 {
207216

208217
[[nodiscard]] bool pack_array_header(std::size_t const n) {
209218
if (n < 16) {
210-
constexpr auto size_mask = static_cast<std::byte>(0b10010000);
211-
*store_++ = static_cast<std::byte>(n) | size_mask;
219+
constexpr auto size_mask = static_cast<B>(0b10010000);
220+
*store_++ = static_cast<B>(n) | size_mask;
212221
} else if (n < std::numeric_limits<std::uint16_t>::max()) {
213222
emplace_combined(FormatConstants::array16, static_cast<std::uint16_t>(n));
214223
} else if (n < std::numeric_limits<std::uint32_t>::max()) {
@@ -252,8 +261,8 @@ namespace msgpack23 {
252261
void pack_type(T const &value) {
253262
std::size_t size = 0;
254263
std::visit([this, &size](auto const &arg) {
255-
const auto inserter = counting_inserter<std::byte>{size};
256-
Packer<counting_inserter<std::byte> > packer{inserter};
264+
const auto inserter = counting_inserter<B>{size};
265+
Packer<B, counting_inserter<B> > packer{inserter};
257266
packer(arg);
258267
}, value);
259268

@@ -345,7 +354,7 @@ namespace msgpack23 {
345354
if (value > 31 or value < -32) {
346355
emplace_constant(FormatConstants::int8);
347356
}
348-
*store_++ = static_cast<std::byte>(value);
357+
*store_++ = static_cast<B>(value);
349358
}
350359

351360
void pack_type(std::int16_t const &value) {
@@ -383,10 +392,10 @@ namespace msgpack23 {
383392

384393
void pack_type(std::uint8_t const &value) {
385394
if (value < 0x80) {
386-
*store_++ = static_cast<std::byte>(value);
395+
*store_++ = static_cast<B>(value);
387396
} else {
388397
emplace_constant(FormatConstants::uint8);
389-
*store_++ = static_cast<std::byte>(value);
398+
*store_++ = static_cast<B>(value);
390399
}
391400
}
392401

@@ -437,10 +446,10 @@ namespace msgpack23 {
437446

438447
void pack_type(std::string const &value) {
439448
if (value.size() < 32) {
440-
*store_++ = static_cast<std::byte>(value.size()) | static_cast<std::byte>(0b10100000);
449+
*store_++ = static_cast<B>(value.size()) | static_cast<B>(0b10100000);
441450
} else if (value.size() < std::numeric_limits<std::uint8_t>::max()) {
442451
emplace_constant(FormatConstants::str8);
443-
*store_++ = static_cast<std::byte>(value.size());
452+
*store_++ = static_cast<B>(value.size());
444453
} else if (value.size() < std::numeric_limits<std::uint16_t>::max()) {
445454
emplace_combined(FormatConstants::str16, static_cast<std::uint16_t>(value.size()));
446455
} else if (value.size() < std::numeric_limits<std::uint32_t>::max()) {
@@ -449,38 +458,44 @@ namespace msgpack23 {
449458
throw std::length_error("String is too long to be serialized.");
450459
}
451460

452-
std::copy(reinterpret_cast<std::byte const * const>(value.data()),
453-
reinterpret_cast<std::byte const * const>(value.data() + value.size()), store_);
461+
std::copy(reinterpret_cast<B const * const>(value.data()),
462+
reinterpret_cast<B const * const>(value.data() + value.size()), store_);
454463
}
455464

456-
void pack_type(std::vector<std::byte> const &value) {
465+
void pack_type(std::vector<B> const &value) {
457466
if (value.size() < std::numeric_limits<std::uint8_t>::max()) {
458467
emplace_constant(FormatConstants::bin8);
459-
*store_++ = static_cast<std::byte>(value.size());
468+
*store_++ = static_cast<B>(value.size());
460469
} else if (value.size() < std::numeric_limits<std::uint16_t>::max()) {
461470
emplace_combined(FormatConstants::bin16, static_cast<std::uint16_t>(value.size()));
462471
} else if (value.size() < std::numeric_limits<std::uint32_t>::max()) {
463472
emplace_combined(FormatConstants::bin32, static_cast<std::uint32_t>(value.size()));
464473
} else {
465474
throw std::length_error("Vector is too long to be serialized.");
466475
}
467-
std::copy(reinterpret_cast<std::byte const * const>(value.data()),
468-
reinterpret_cast<std::byte const * const>(value.data() + value.size()), store_);
476+
std::copy(reinterpret_cast<B const * const>(value.data()),
477+
reinterpret_cast<B const * const>(value.data() + value.size()), store_);
469478
}
470479
};
471480

481+
template<typename Container>
482+
requires byte_type<typename Container::value_type>
483+
Packer(std::back_insert_iterator<Container>) ->
484+
Packer<typename Container::value_type, std::back_insert_iterator<Container> >;
485+
472486
template<typename T, typename P>
473-
concept PackableObject = requires(T t, P p)
487+
concept packable_object = requires(T t, P p)
474488
{
475489
{ t.pack(p) };
476490
};
477491

492+
template<byte_type B>
478493
class Unpacker final {
479494
public:
480495
Unpacker() : data_() {
481496
}
482497

483-
explicit Unpacker(std::span<std::byte const> const data) : data_(data) {
498+
explicit Unpacker(std::span<B const> const data) : data_(data) {
484499
}
485500

486501
template<typename... Types>
@@ -489,12 +504,12 @@ namespace msgpack23 {
489504
}
490505

491506
private:
492-
std::span<std::byte const> data_;
507+
std::span<B const> data_;
493508
std::size_t position_{0};
494509

495510
[[nodiscard]] std::byte current() const {
496511
if (position_ < data_.size()) {
497-
return data_[position_];
512+
return static_cast<std::byte>(data_[position_]);
498513
}
499514
throw std::out_of_range("Unpacker doesn't have enough data.");
500515
}
@@ -514,7 +529,7 @@ namespace msgpack23 {
514529
return static_cast<FormatConstants>(std::to_integer<std::uint8_t>(current()));
515530
}
516531

517-
template<typename T, std::enable_if_t<std::is_unsigned_v<T>, int> = 0>
532+
template<typename T, std::enable_if_t<std::is_unsigned_v<T>, int> = 0>
518533
[[nodiscard]] T read_integral() {
519534
if (position_ + sizeof(T) > data_.size()) {
520535
throw std::out_of_range("Unpacker doesn't have enough data.");
@@ -976,7 +991,7 @@ namespace msgpack23 {
976991
increment(str_size);
977992
}
978993

979-
void unpack_type(std::vector<std::byte> &value) {
994+
void unpack_type(std::vector<B> &value) {
980995
std::size_t bin_size = 0;
981996
if (read_conditional<FormatConstants::bin32, std::uint32_t>(bin_size)
982997
or read_conditional<FormatConstants::bin16, std::uint16_t>(bin_size)
@@ -987,29 +1002,58 @@ namespace msgpack23 {
9871002
if (position_ + bin_size > data_.size()) {
9881003
throw std::out_of_range("Vector position is out of range");
9891004
}
990-
auto const *src = reinterpret_cast<std::byte const *>(data_.data() + position_);
1005+
auto const *src = data_.data() + position_;
9911006
value.assign(src, src + bin_size);
9921007
increment(bin_size);
9931008
}
9941009
};
9951010

9961011
template<typename T>
997-
concept UnpackableObject = requires(T t, Unpacker u)
1012+
Unpacker(std::span<T const>) -> Unpacker<std::remove_const_t<T> >;
1013+
1014+
template<typename T>
1015+
concept container = requires (T b) {
1016+
typename T::value_type;
1017+
} && byte_type<typename T::value_type>;
1018+
1019+
template<container Container>
1020+
Unpacker(Container const &) -> Unpacker<typename Container::value_type>;
1021+
1022+
template<typename T, typename U>
1023+
concept unpackable_object = requires(T t, U u)
9981024
{
9991025
t.unpack(u);
10001026
};
10011027

1002-
template<std::output_iterator<std::byte> Iter, PackableObject<Packer<Iter> > PackableObject>
1028+
template<byte_type B, std::output_iterator<B> Iter, packable_object<Packer<B, Iter> > PackableObject>
10031029
void pack(Iter iterator, PackableObject const &obj) {
1004-
Packer<Iter> packer{iterator};
1030+
Packer<B, Iter> packer{iterator};
10051031
return obj.pack(packer);
10061032
}
10071033

1008-
template<UnpackableObject UnpackableObject>
1009-
[[nodiscard]] UnpackableObject unpack(std::span<const std::byte> const data) {
1034+
template<container Container, packable_object<Packer<typename Container::value_type, std::back_insert_iterator<Container>>> PackableObject>
1035+
void pack(std::back_insert_iterator<Container> iterator, PackableObject const &obj) {
1036+
return pack<typename Container::value_type, std::back_insert_iterator<Container>, PackableObject>(iterator, obj);
1037+
}
1038+
1039+
template<typename UnpackableObject, byte_type B>
1040+
requires unpackable_object<UnpackableObject, Unpacker<B>>
1041+
[[nodiscard]] UnpackableObject unpack(std::span<B const> const data) {
10101042
Unpacker unpacker(data);
10111043
UnpackableObject obj{};
10121044
obj.unpack(unpacker);
10131045
return obj;
10141046
}
1047+
1048+
template<typename T>
1049+
concept span_convertible = std::ranges::contiguous_range<T>
1050+
&& std::ranges::sized_range<T>
1051+
&& byte_type<std::remove_const_t<std::ranges::range_value_t<T>>>;
1052+
1053+
template<typename UnpackableObject, span_convertible Container>
1054+
requires unpackable_object<UnpackableObject, Unpacker<std::remove_const_t<std::ranges::range_value_t<Container>>>>
1055+
[[nodiscard]] UnpackableObject unpack(Container const& data) {
1056+
using B = std::remove_const_t<std::ranges::range_value_t<Container>>;
1057+
return unpack<UnpackableObject>(std::span<B const>{data});
1058+
}
10151059
}

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ find_package(GTest REQUIRED)
77
add_executable(
88
msgpack23_tests
99
array_tests.cpp
10+
byte_type_tests.cpp
1011
chrono_tests.cpp
1112
exception_tests.cpp
1213
int8_tests.cpp

0 commit comments

Comments
 (0)