From 86662623ec2b4af15e181a02ac57fade26bdb3a6 Mon Sep 17 00:00:00 2001 From: yuvicc Date: Wed, 1 Apr 2026 20:11:57 +0530 Subject: [PATCH] Add `SpanWriter` class for zero-allocation stream writing Co-authored-by: stickies-v --- src/streams.h | 32 ++++++++++++++++++++++++++++++++ src/test/streams_tests.cpp | 22 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/streams.h b/src/streams.h index 466084e9fa0..12a2cac2c79 100644 --- a/src/streams.h +++ b/src/streams.h @@ -121,6 +121,38 @@ public: } }; +/** Minimal stream for writing to an existing span of bytes. + */ +class SpanWriter +{ +private: + std::span m_dest; + +public: + explicit SpanWriter(std::span dest) : m_dest{dest} {} + template + SpanWriter(std::span dest, Args&&... args) : SpanWriter{dest} + { + ::SerializeMany(*this, std::forward(args)...); + } + + void write(std::span src) + { + if (src.size() > m_dest.size()) { + throw std::ios_base::failure("SpanWriter::write(): exceeded buffer size"); + } + memcpy(m_dest.data(), src.data(), src.size()); + m_dest = m_dest.subspan(src.size()); + } + + template + SpanWriter& operator<<(const T& obj) + { + ::Serialize(*this, obj); + return *this; + } +}; + /** Double ended buffer combining vector and stream-like interfaces. * * >> and << read and write unformatted data using the above serialization templates. diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index af75ee987ad..cb1ca6eeddf 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -207,6 +207,28 @@ BOOST_AUTO_TEST_CASE(streams_vector_writer) vch.clear(); } +BOOST_AUTO_TEST_CASE(streams_span_writer) +{ + unsigned char a(1); + unsigned char b(2); + unsigned char bytes[] = {3, 4, 5, 6}; + std::array arr{}; + + // Test operator<< + SpanWriter writer{arr}; + writer << a << b; + BOOST_CHECK_EQUAL(HexStr(arr), "0102000000000000"); + + // Use variadic constructor and write to subspan. + SpanWriter{std::span{arr}.subspan(2), a, bytes, b}; + BOOST_CHECK_EQUAL(HexStr(arr), "0102010304050602"); + + // Writing past the end throws + std::array small{}; + BOOST_CHECK_THROW(SpanWriter(std::span{small}, a, b), std::ios_base::failure); + BOOST_CHECK_THROW(SpanWriter(std::span{small}) << a << b, std::ios_base::failure); +} + BOOST_AUTO_TEST_CASE(streams_vector_reader) { std::vector vch = {1, 255, 3, 4, 5, 6};