From a1b7c5ad20bfabc3612022723e4ab458eda24e9d Mon Sep 17 00:00:00 2001 From: Vasily Chekalkin Date: Sat, 28 Feb 2026 21:33:51 +1100 Subject: [PATCH] Add zero-copy unpack_type overload for std::span MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The existing binary unpack path copies data into std::vector: value.assign(src, src + bin_size); // always allocates + copies For callers that only need to read the binary payload (e.g. pass it to a parser like GEOS WKBReader), this allocation is pure waste — the data is already contiguous in the Unpacker's internal buffer. ## Solution Add a second unpack_type overload that resolves when the caller declares their argument as std::span: template void unpack_type(std::span &value) { // ... read header to get bin_size ... value = data_.subspan(position_, bin_size); // zero-copy view increment(bin_size); } data_.subspan() returns a non-owning view into the Unpacker's own buffer, so no allocation or memcpy occurs. The span remains valid for the lifetime of the Unpacker (i.e. for the duration of the unpack call chain), which is sufficient for immediate-use patterns like deserialise → parse → discard. ## Safety - Bounds are checked identically to the vector overload (position_ + bin_size > data_.size() throws std::out_of_range). - The returned span is const-qualified (B const), preventing accidental writes back into the source buffer. - Callers that need to own the data should continue using std::vector. ## Backwards compatibility Fully additive — existing code using std::vector is unaffected. The new overload is only selected when the target variable is declared as std::span. Co-Authored-By: Claude Sonnet 4.6 --- include/msgpack23/msgpack23.h | 18 ++++++++++++ tests/CMakeLists.txt | 1 + tests/span_tests.cpp | 52 +++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 tests/span_tests.cpp diff --git a/include/msgpack23/msgpack23.h b/include/msgpack23/msgpack23.h index 2adfe36..f507ac1 100644 --- a/include/msgpack23/msgpack23.h +++ b/include/msgpack23/msgpack23.h @@ -1006,6 +1006,24 @@ namespace msgpack23 { value.assign(src, src + bin_size); increment(bin_size); } + + // Zero-copy alternative to the vector overload: returns a view into the + // internal buffer instead of copying. Valid only for the Unpacker's lifetime. + template + void unpack_type(std::span &value) { + std::size_t bin_size = 0; + if (read_conditional(bin_size) + or read_conditional(bin_size) + or read_conditional(bin_size)) { + } else { + throw std::logic_error("Unexpected value"); + } + if (position_ + bin_size > data_.size()) { + throw std::out_of_range("Span position is out of range"); + } + value = data_.subspan(position_, bin_size); + increment(bin_size); + } }; template diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4a5fe04..d4513f9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable( main.cpp map_tests.cpp object_packing_tests.cpp + span_tests.cpp string_tests.cpp type_packing_tests.cpp uint8_tests.cpp diff --git a/tests/span_tests.cpp b/tests/span_tests.cpp new file mode 100644 index 0000000..5449d19 --- /dev/null +++ b/tests/span_tests.cpp @@ -0,0 +1,52 @@ +// +// Created by Neara Software on 28/02/2026. +// + +#include +#include + +namespace { + class msgpack23_span : public testing::TestWithParam { + }; + + TEST_P(msgpack23_span, binarySpanRoundTrip) { + std::vector expected{}; + for (std::size_t i = 0; i < GetParam(); ++i) { + expected.emplace_back(static_cast(i)); + } + std::vector data{}; + msgpack23::Packer packer{std::back_insert_iterator(data)}; + packer(expected); + + msgpack23::Unpacker unpacker{data}; + std::span actual{}; + unpacker(actual); + + ASSERT_EQ(actual.size(), expected.size()); + EXPECT_TRUE(std::equal(actual.begin(), actual.end(), expected.begin())); + } + + TEST_P(msgpack23_span, binarySpanIsZeroCopy) { + std::vector expected(GetParam(), 0x42); + std::vector data{}; + msgpack23::Packer packer{std::back_insert_iterator(data)}; + packer(expected); + + msgpack23::Unpacker unpacker{data}; + std::span actual{}; + unpacker(actual); + + // Span must point into the packed buffer, not a separate allocation + EXPECT_GE(actual.data(), data.data()); + EXPECT_LE(actual.data() + actual.size(), data.data() + data.size()); + } + + constexpr std::size_t span_sizes[] = { + 1, + std::numeric_limits::max() - 1, // bin8 near-max + std::numeric_limits::max() + 1, // bin16 boundary + std::numeric_limits::max() - 1, // bin16 near-max + std::numeric_limits::max() + 1, // bin32 boundary + }; + INSTANTIATE_TEST_SUITE_P(SomeValuesTest, msgpack23_span, testing::ValuesIn(span_sizes)); +}