From 8338803b88c372e97459f7ff6b1d7b7241f1c555 Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Mon, 15 Jun 2026 11:59:50 -0600 Subject: [PATCH] fix(buffers): make_buffer returns mutable_buffer for by-value mutable ranges The generic mutable overload took an lvalue reference `make_buffer(T&)`, so a `boost::span` passed as a temporary could not bind to it and fell through to the const overload, returning a `const_buffer` (#147). `std::span` only escaped this via a dedicated by-value overload. Change the generic mutable overloads to a forwarding reference `make_buffer(T&&)` so by-value mutable contiguous ranges bind there and return `mutable_buffer`. Remove the now-redundant explicit C-array, std::array, std::vector, std::basic_string, and std::span overloads, which the concept-based generic overloads fully subsume; prune the includes they required. Behavior is preserved for all non-empty inputs; for empty array/ const-string/span the returned (size-0) buffer's data pointer is now normalized to nullptr, matching vector/string. Add a prvalue std::span regression test plus a boost::span test guarded by __has_include so Capy still builds and passes without Boost present. --- .../ROOT/pages/5.buffers/5b.types.adoc | 8 + include/boost/capy/buffers/make_buffer.hpp | 308 +----------------- include/boost/capy/test/buffer_source.hpp | 1 + test/unit/buffers/buffer_param.cpp | 1 + test/unit/buffers/make_buffer.cpp | 153 ++++++++- test/unit/concept/buffer_source.cpp | 1 + test/unit/io/any_buffer_sink.cpp | 1 + test/unit/io/any_buffer_source.cpp | 1 + test/unit/io/any_read_stream.cpp | 1 + test/unit/io/any_write_stream.cpp | 1 + test/unit/test/buffer_sink.cpp | 1 + test/unit/test/buffer_source.cpp | 1 + 12 files changed, 181 insertions(+), 297 deletions(-) diff --git a/doc/modules/ROOT/pages/5.buffers/5b.types.adoc b/doc/modules/ROOT/pages/5.buffers/5b.types.adoc index dc759112b..b4574f231 100644 --- a/doc/modules/ROOT/pages/5.buffers/5b.types.adoc +++ b/doc/modules/ROOT/pages/5.buffers/5b.types.adoc @@ -140,8 +140,16 @@ auto buf = make_buffer(str); // From std::string_view std::string_view sv = "hello"; auto buf = make_buffer(sv); + +// From a span (std::span or boost::span) +std::span sp(arr); +auto buf = make_buffer(sp); ---- +`make_buffer` accepts any sized, contiguous range of trivially-copyable +elements—including `std::span` and `boost::span`—in addition to the +sources shown above. + The returned buffer type depends on constness: * Non-const containers → `mutable_buffer` diff --git a/include/boost/capy/buffers/make_buffer.hpp b/include/boost/capy/buffers/make_buffer.hpp index 791d2c353..f4b288ad9 100644 --- a/include/boost/capy/buffers/make_buffer.hpp +++ b/include/boost/capy/buffers/make_buffer.hpp @@ -124,242 +124,6 @@ make_buffer( size < max_size ? size : max_size); } -/** Return a buffer from a C-style array. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -mutable_buffer -make_buffer( - T (&data)[N]) noexcept -{ - return mutable_buffer( - data, N * sizeof(T)); -} - -/** Return a buffer from a C-style array with a maximum size. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -mutable_buffer -make_buffer( - T (&data)[N], - std::size_t max_size) noexcept -{ - return mutable_buffer( - data, - N * sizeof(T) < max_size ? N * sizeof(T) : max_size); -} - -/** Return a buffer from a const C-style array. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -const_buffer -make_buffer( - T const (&data)[N]) noexcept -{ - return const_buffer( - data, N * sizeof(T)); -} - -/** Return a buffer from a const C-style array with a maximum size. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -const_buffer -make_buffer( - T const (&data)[N], - std::size_t max_size) noexcept -{ - return const_buffer( - data, - N * sizeof(T) < max_size ? N * sizeof(T) : max_size); -} - -// std::array - -/** Return a buffer from a std::array. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -mutable_buffer -make_buffer( - std::array& data) noexcept -{ - return mutable_buffer( - data.data(), data.size() * sizeof(T)); -} - -/** Return a buffer from a std::array with a maximum size. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -mutable_buffer -make_buffer( - std::array& data, - std::size_t max_size) noexcept -{ - return mutable_buffer( - data.data(), - data.size() * sizeof(T) < max_size - ? data.size() * sizeof(T) : max_size); -} - -/** Return a buffer from a const std::array. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -const_buffer -make_buffer( - std::array const& data) noexcept -{ - return const_buffer( - data.data(), data.size() * sizeof(T)); -} - -/** Return a buffer from a const std::array with a maximum size. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -const_buffer -make_buffer( - std::array const& data, - std::size_t max_size) noexcept -{ - return const_buffer( - data.data(), - data.size() * sizeof(T) < max_size - ? data.size() * sizeof(T) : max_size); -} - -// std::vector - -/** Return a buffer from a std::vector. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -mutable_buffer -make_buffer( - std::vector& data) noexcept -{ - return mutable_buffer( - data.size() ? data.data() : nullptr, - data.size() * sizeof(T)); -} - -/** Return a buffer from a std::vector with a maximum size. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -mutable_buffer -make_buffer( - std::vector& data, - std::size_t max_size) noexcept -{ - return mutable_buffer( - data.size() ? data.data() : nullptr, - data.size() * sizeof(T) < max_size - ? data.size() * sizeof(T) : max_size); -} - -/** Return a buffer from a const std::vector. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -const_buffer -make_buffer( - std::vector const& data) noexcept -{ - return const_buffer( - data.size() ? data.data() : nullptr, - data.size() * sizeof(T)); -} - -/** Return a buffer from a const std::vector with a maximum size. -*/ -template - requires std::is_trivially_copyable_v -[[nodiscard]] -const_buffer -make_buffer( - std::vector const& data, - std::size_t max_size) noexcept -{ - return const_buffer( - data.size() ? data.data() : nullptr, - data.size() * sizeof(T) < max_size - ? data.size() * sizeof(T) : max_size); -} - -// std::basic_string - -/** Return a buffer from a std::basic_string. -*/ -template -[[nodiscard]] -mutable_buffer -make_buffer( - std::basic_string& data) noexcept -{ - return mutable_buffer( - data.size() ? &data[0] : nullptr, - data.size() * sizeof(CharT)); -} - -/** Return a buffer from a std::basic_string with a maximum size. -*/ -template -[[nodiscard]] -mutable_buffer -make_buffer( - std::basic_string& data, - std::size_t max_size) noexcept -{ - return mutable_buffer( - data.size() ? &data[0] : nullptr, - data.size() * sizeof(CharT) < max_size - ? data.size() * sizeof(CharT) : max_size); -} - -/** Return a buffer from a const std::basic_string. -*/ -template -[[nodiscard]] -const_buffer -make_buffer( - std::basic_string const& data) noexcept -{ - return const_buffer( - data.data(), - data.size() * sizeof(CharT)); -} - -/** Return a buffer from a const std::basic_string with a maximum size. -*/ -template -[[nodiscard]] -const_buffer -make_buffer( - std::basic_string const& data, - std::size_t max_size) noexcept -{ - return const_buffer( - data.data(), - data.size() * sizeof(CharT) < max_size - ? data.size() * sizeof(CharT) : max_size); -} - // std::basic_string_view /** Return a buffer from a std::basic_string_view. @@ -390,62 +154,6 @@ make_buffer( ? data.size() * sizeof(CharT) : max_size); } -// std::span - -/** Return a buffer from a mutable std::span. -*/ -template - requires (!std::is_const_v && sizeof(T) == 1) -[[nodiscard]] -mutable_buffer -make_buffer( - std::span data) noexcept -{ - return mutable_buffer(data.data(), data.size()); -} - -/** Return a buffer from a mutable std::span with a maximum size. -*/ -template - requires (!std::is_const_v && sizeof(T) == 1) -[[nodiscard]] -mutable_buffer -make_buffer( - std::span data, - std::size_t max_size) noexcept -{ - return mutable_buffer( - data.data(), - data.size() < max_size ? data.size() : max_size); -} - -/** Return a buffer from a const std::span. -*/ -template - requires (sizeof(T) == 1) -[[nodiscard]] -const_buffer -make_buffer( - std::span data) noexcept -{ - return const_buffer(data.data(), data.size()); -} - -/** Return a buffer from a const std::span with a maximum size. -*/ -template - requires (sizeof(T) == 1) -[[nodiscard]] -const_buffer -make_buffer( - std::span data, - std::size_t max_size) noexcept -{ - return const_buffer( - data.data(), - data.size() < max_size ? data.size() : max_size); -} - // Contiguous ranges namespace detail { @@ -473,11 +181,17 @@ concept const_contiguous_range = } // detail /** Return a buffer from a mutable contiguous range. + + Accepts any sized, contiguous range of trivially-copyable, + non-const elements, including `std::vector`, `std::array`, + `std::string`, `std::span`, `boost::span`, and built-in arrays, + whether passed as an lvalue or a temporary. The returned buffer + refers to the range's storage, which must outlive the buffer. */ template [[nodiscard]] mutable_buffer -make_buffer(T& data) noexcept +make_buffer(T&& data) noexcept { return mutable_buffer( std::ranges::size(data) ? std::ranges::data(data) : nullptr, @@ -490,7 +204,7 @@ template [[nodiscard]] mutable_buffer make_buffer( - T& data, + T&& data, std::size_t max_size) noexcept { auto const n = std::ranges::size(data) * sizeof(std::ranges::range_value_t); @@ -500,6 +214,12 @@ make_buffer( } /** Return a buffer from a const contiguous range. + + Accepts any sized, contiguous range of trivially-copyable + elements with const access, including const `std::vector`, + `std::array`, `std::string`, `std::span`, `boost::span`, and + string literals. The returned buffer refers to the range's + storage, which must outlive the buffer. */ template [[nodiscard]] diff --git a/include/boost/capy/test/buffer_source.hpp b/include/boost/capy/test/buffer_source.hpp index 008b80753..f80ad244d 100644 --- a/include/boost/capy/test/buffer_source.hpp +++ b/include/boost/capy/test/buffer_source.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include diff --git a/test/unit/buffers/buffer_param.cpp b/test/unit/buffers/buffer_param.cpp index 9859bc601..24f57eecb 100644 --- a/test/unit/buffers/buffer_param.cpp +++ b/test/unit/buffers/buffer_param.cpp @@ -13,6 +13,7 @@ #include "test_buffers.hpp" #include +#include #include #include diff --git a/test/unit/buffers/make_buffer.cpp b/test/unit/buffers/make_buffer.cpp index bcc0cd9a9..e23d9af7d 100644 --- a/test/unit/buffers/make_buffer.cpp +++ b/test/unit/buffers/make_buffer.cpp @@ -16,8 +16,25 @@ #include #include #include +#include #include +// boost::span is the type reported in issue #147. It lives in Boost.Core, +// which is on the include path in the full Boost build (and CI) but not in +// the minimal standalone CMake build, so guard on availability. +#if __has_include() +#include +#define BOOST_CAPY_TEST_HAS_BOOST_SPAN +#endif + +// Statically assert the exact return type of a make_buffer(...) call. +// This is the regression guard for issue #147: a silent return-type change +// is caught at compile time. It matters most for const cases, since a +// mutable_buffer implicitly converts to const_buffer, so a const case that +// wrongly became mutable would still compile in a plain assignment. +#define CAPY_ASSERT_RETURNS(Type, ...) \ + static_assert(std::is_same_v) + namespace boost { namespace capy { @@ -31,6 +48,7 @@ struct make_buffer_test // make_buffer(mutable_buffer) { mutable_buffer b(buf, 10); + CAPY_ASSERT_RETURNS(mutable_buffer, b); auto b1 = make_buffer(b); BOOST_TEST_EQ(b1.data(), b.data()); BOOST_TEST_EQ(b1.size(), b.size()); @@ -39,6 +57,7 @@ struct make_buffer_test // make_buffer(mutable_buffer, max_size) - no truncation { mutable_buffer b(buf, 10); + CAPY_ASSERT_RETURNS(mutable_buffer, b, 20); auto b1 = make_buffer(b, 20); BOOST_TEST_EQ(b1.data(), b.data()); BOOST_TEST_EQ(b1.size(), 10u); @@ -47,6 +66,7 @@ struct make_buffer_test // make_buffer(mutable_buffer, max_size) - truncation { mutable_buffer b(buf, 10); + CAPY_ASSERT_RETURNS(mutable_buffer, b, 5); auto b1 = make_buffer(b, 5); BOOST_TEST_EQ(b1.data(), b.data()); BOOST_TEST_EQ(b1.size(), 5u); @@ -61,6 +81,7 @@ struct make_buffer_test // make_buffer(const_buffer) { const_buffer b(buf, 10); + CAPY_ASSERT_RETURNS(const_buffer, b); auto b1 = make_buffer(b); BOOST_TEST_EQ(b1.data(), b.data()); BOOST_TEST_EQ(b1.size(), b.size()); @@ -69,6 +90,7 @@ struct make_buffer_test // make_buffer(const_buffer, max_size) - no truncation { const_buffer b(buf, 10); + CAPY_ASSERT_RETURNS(const_buffer, b, 20); auto b1 = make_buffer(b, 20); BOOST_TEST_EQ(b1.data(), b.data()); BOOST_TEST_EQ(b1.size(), 10u); @@ -77,6 +99,7 @@ struct make_buffer_test // make_buffer(const_buffer, max_size) - truncation { const_buffer b(buf, 10); + CAPY_ASSERT_RETURNS(const_buffer, b, 5); auto b1 = make_buffer(b, 5); BOOST_TEST_EQ(b1.data(), b.data()); BOOST_TEST_EQ(b1.size(), 5u); @@ -87,31 +110,36 @@ struct make_buffer_test testRawPointer() { char buf[10]{}; + char* pbuf = buf; char const* cbuf = buf; // make_buffer(void*, size) { - auto b = make_buffer(buf, 10); + CAPY_ASSERT_RETURNS(mutable_buffer, pbuf, 10); + auto b = make_buffer(pbuf, 10); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 10u); } // make_buffer(void*, size, max_size) - no truncation { - auto b = make_buffer(buf, 10, 20); + CAPY_ASSERT_RETURNS(mutable_buffer, pbuf, 10, 20); + auto b = make_buffer(pbuf, 10, 20); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 10u); } // make_buffer(void*, size, max_size) - truncation { - auto b = make_buffer(buf, 10, 5); + CAPY_ASSERT_RETURNS(mutable_buffer, pbuf, 10, 5); + auto b = make_buffer(pbuf, 10, 5); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 5u); } // make_buffer(void const*, size) { + CAPY_ASSERT_RETURNS(const_buffer, cbuf, 10); auto b = make_buffer(cbuf, 10); BOOST_TEST_EQ(b.data(), cbuf); BOOST_TEST_EQ(b.size(), 10u); @@ -119,6 +147,7 @@ struct make_buffer_test // make_buffer(void const*, size, max_size) - no truncation { + CAPY_ASSERT_RETURNS(const_buffer, cbuf, 10, 20); auto b = make_buffer(cbuf, 10, 20); BOOST_TEST_EQ(b.data(), cbuf); BOOST_TEST_EQ(b.size(), 10u); @@ -126,6 +155,7 @@ struct make_buffer_test // make_buffer(void const*, size, max_size) - truncation { + CAPY_ASSERT_RETURNS(const_buffer, cbuf, 10, 5); auto b = make_buffer(cbuf, 10, 5); BOOST_TEST_EQ(b.data(), cbuf); BOOST_TEST_EQ(b.size(), 5u); @@ -140,6 +170,7 @@ struct make_buffer_test // make_buffer(T(&)[N]) { + CAPY_ASSERT_RETURNS(mutable_buffer, buf); mutable_buffer b = make_buffer(buf); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 10u); @@ -147,6 +178,7 @@ struct make_buffer_test // make_buffer(T(&)[N], max_size) - no truncation { + CAPY_ASSERT_RETURNS(mutable_buffer, buf, 20); mutable_buffer b = make_buffer(buf, 20); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 10u); @@ -154,6 +186,7 @@ struct make_buffer_test // make_buffer(T(&)[N], max_size) - truncation { + CAPY_ASSERT_RETURNS(mutable_buffer, buf, 5); mutable_buffer b = make_buffer(buf, 5); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 5u); @@ -161,6 +194,7 @@ struct make_buffer_test // make_buffer(T const(&)[N]) { + CAPY_ASSERT_RETURNS(const_buffer, cbuf); const_buffer b = make_buffer(cbuf); BOOST_TEST_EQ(b.data(), cbuf); BOOST_TEST_EQ(b.size(), 10u); @@ -168,6 +202,7 @@ struct make_buffer_test // make_buffer(T const(&)[N], max_size) - no truncation { + CAPY_ASSERT_RETURNS(const_buffer, cbuf, 20); const_buffer b = make_buffer(cbuf, 20); BOOST_TEST_EQ(b.data(), cbuf); BOOST_TEST_EQ(b.size(), 10u); @@ -175,6 +210,7 @@ struct make_buffer_test // make_buffer(T const(&)[N], max_size) - truncation { + CAPY_ASSERT_RETURNS(const_buffer, cbuf, 5); const_buffer b = make_buffer(cbuf, 5); BOOST_TEST_EQ(b.data(), cbuf); BOOST_TEST_EQ(b.size(), 5u); @@ -183,6 +219,7 @@ struct make_buffer_test // Multi-byte element type { int ibuf[5]{}; + CAPY_ASSERT_RETURNS(mutable_buffer, ibuf); mutable_buffer b = make_buffer(ibuf); BOOST_TEST_EQ(b.data(), ibuf); BOOST_TEST_EQ(b.size(), 5u * sizeof(int)); @@ -197,6 +234,7 @@ struct make_buffer_test // make_buffer(std::array&) { + CAPY_ASSERT_RETURNS(mutable_buffer, arr); mutable_buffer b = make_buffer(arr); BOOST_TEST_EQ(b.data(), arr.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -204,6 +242,7 @@ struct make_buffer_test // make_buffer(std::array&, max_size) - no truncation { + CAPY_ASSERT_RETURNS(mutable_buffer, arr, 20); mutable_buffer b = make_buffer(arr, 20); BOOST_TEST_EQ(b.data(), arr.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -211,6 +250,7 @@ struct make_buffer_test // make_buffer(std::array&, max_size) - truncation { + CAPY_ASSERT_RETURNS(mutable_buffer, arr, 5); mutable_buffer b = make_buffer(arr, 5); BOOST_TEST_EQ(b.data(), arr.data()); BOOST_TEST_EQ(b.size(), 5u); @@ -218,6 +258,7 @@ struct make_buffer_test // make_buffer(std::array const&) { + CAPY_ASSERT_RETURNS(const_buffer, carr); const_buffer b = make_buffer(carr); BOOST_TEST_EQ(b.data(), carr.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -225,6 +266,7 @@ struct make_buffer_test // make_buffer(std::array const&, max_size) - no truncation { + CAPY_ASSERT_RETURNS(const_buffer, carr, 20); const_buffer b = make_buffer(carr, 20); BOOST_TEST_EQ(b.data(), carr.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -232,6 +274,7 @@ struct make_buffer_test // make_buffer(std::array const&, max_size) - truncation { + CAPY_ASSERT_RETURNS(const_buffer, carr, 5); const_buffer b = make_buffer(carr, 5); BOOST_TEST_EQ(b.data(), carr.data()); BOOST_TEST_EQ(b.size(), 5u); @@ -240,6 +283,7 @@ struct make_buffer_test // Multi-byte element type { std::array iarr{}; + CAPY_ASSERT_RETURNS(mutable_buffer, iarr); mutable_buffer b = make_buffer(iarr); BOOST_TEST_EQ(b.data(), iarr.data()); BOOST_TEST_EQ(b.size(), 5u * sizeof(int)); @@ -255,6 +299,7 @@ struct make_buffer_test // make_buffer(std::vector&) { + CAPY_ASSERT_RETURNS(mutable_buffer, vec); mutable_buffer b = make_buffer(vec); BOOST_TEST_EQ(b.data(), vec.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -262,6 +307,7 @@ struct make_buffer_test // make_buffer(std::vector&, max_size) - no truncation { + CAPY_ASSERT_RETURNS(mutable_buffer, vec, 20); mutable_buffer b = make_buffer(vec, 20); BOOST_TEST_EQ(b.data(), vec.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -269,6 +315,7 @@ struct make_buffer_test // make_buffer(std::vector&, max_size) - truncation { + CAPY_ASSERT_RETURNS(mutable_buffer, vec, 5); mutable_buffer b = make_buffer(vec, 5); BOOST_TEST_EQ(b.data(), vec.data()); BOOST_TEST_EQ(b.size(), 5u); @@ -276,6 +323,7 @@ struct make_buffer_test // make_buffer(std::vector const&) { + CAPY_ASSERT_RETURNS(const_buffer, cvec); const_buffer b = make_buffer(cvec); BOOST_TEST_EQ(b.data(), cvec.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -283,6 +331,7 @@ struct make_buffer_test // make_buffer(std::vector const&, max_size) - no truncation { + CAPY_ASSERT_RETURNS(const_buffer, cvec, 20); const_buffer b = make_buffer(cvec, 20); BOOST_TEST_EQ(b.data(), cvec.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -290,6 +339,7 @@ struct make_buffer_test // make_buffer(std::vector const&, max_size) - truncation { + CAPY_ASSERT_RETURNS(const_buffer, cvec, 5); const_buffer b = make_buffer(cvec, 5); BOOST_TEST_EQ(b.data(), cvec.data()); BOOST_TEST_EQ(b.size(), 5u); @@ -297,6 +347,7 @@ struct make_buffer_test // Empty vector { + CAPY_ASSERT_RETURNS(mutable_buffer, empty_vec); mutable_buffer b = make_buffer(empty_vec); BOOST_TEST_EQ(b.data(), nullptr); BOOST_TEST_EQ(b.size(), 0u); @@ -305,6 +356,7 @@ struct make_buffer_test // Multi-byte element type { std::vector ivec(5); + CAPY_ASSERT_RETURNS(mutable_buffer, ivec); mutable_buffer b = make_buffer(ivec); BOOST_TEST_EQ(b.data(), ivec.data()); BOOST_TEST_EQ(b.size(), 5u * sizeof(int)); @@ -320,6 +372,7 @@ struct make_buffer_test // make_buffer(std::string&) { + CAPY_ASSERT_RETURNS(mutable_buffer, str); mutable_buffer b = make_buffer(str); BOOST_TEST_EQ(b.data(), &str[0]); BOOST_TEST_EQ(b.size(), 10u); @@ -327,6 +380,7 @@ struct make_buffer_test // make_buffer(std::string&, max_size) - no truncation { + CAPY_ASSERT_RETURNS(mutable_buffer, str, 20); mutable_buffer b = make_buffer(str, 20); BOOST_TEST_EQ(b.data(), &str[0]); BOOST_TEST_EQ(b.size(), 10u); @@ -334,6 +388,7 @@ struct make_buffer_test // make_buffer(std::string&, max_size) - truncation { + CAPY_ASSERT_RETURNS(mutable_buffer, str, 5); mutable_buffer b = make_buffer(str, 5); BOOST_TEST_EQ(b.data(), &str[0]); BOOST_TEST_EQ(b.size(), 5u); @@ -341,6 +396,7 @@ struct make_buffer_test // make_buffer(std::string const&) { + CAPY_ASSERT_RETURNS(const_buffer, cstr); const_buffer b = make_buffer(cstr); BOOST_TEST_EQ(b.data(), cstr.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -348,6 +404,7 @@ struct make_buffer_test // make_buffer(std::string const&, max_size) - no truncation { + CAPY_ASSERT_RETURNS(const_buffer, cstr, 20); const_buffer b = make_buffer(cstr, 20); BOOST_TEST_EQ(b.data(), cstr.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -355,6 +412,7 @@ struct make_buffer_test // make_buffer(std::string const&, max_size) - truncation { + CAPY_ASSERT_RETURNS(const_buffer, cstr, 5); const_buffer b = make_buffer(cstr, 5); BOOST_TEST_EQ(b.data(), cstr.data()); BOOST_TEST_EQ(b.size(), 5u); @@ -362,6 +420,7 @@ struct make_buffer_test // Empty string { + CAPY_ASSERT_RETURNS(mutable_buffer, empty_str); mutable_buffer b = make_buffer(empty_str); BOOST_TEST_EQ(b.data(), nullptr); BOOST_TEST_EQ(b.size(), 0u); @@ -370,6 +429,7 @@ struct make_buffer_test // Wide string { std::wstring wstr = L"hello"; + CAPY_ASSERT_RETURNS(mutable_buffer, wstr); mutable_buffer b = make_buffer(wstr); BOOST_TEST_EQ(b.data(), &wstr[0]); BOOST_TEST_EQ(b.size(), 5u * sizeof(wchar_t)); @@ -384,6 +444,7 @@ struct make_buffer_test // make_buffer(std::string_view) { + CAPY_ASSERT_RETURNS(const_buffer, sv); const_buffer b = make_buffer(sv); BOOST_TEST_EQ(b.data(), sv.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -391,6 +452,7 @@ struct make_buffer_test // make_buffer(std::string_view, max_size) - no truncation { + CAPY_ASSERT_RETURNS(const_buffer, sv, 20); const_buffer b = make_buffer(sv, 20); BOOST_TEST_EQ(b.data(), sv.data()); BOOST_TEST_EQ(b.size(), 10u); @@ -398,6 +460,7 @@ struct make_buffer_test // make_buffer(std::string_view, max_size) - truncation { + CAPY_ASSERT_RETURNS(const_buffer, sv, 5); const_buffer b = make_buffer(sv, 5); BOOST_TEST_EQ(b.data(), sv.data()); BOOST_TEST_EQ(b.size(), 5u); @@ -405,6 +468,7 @@ struct make_buffer_test // Empty string_view { + CAPY_ASSERT_RETURNS(const_buffer, empty_sv); const_buffer b = make_buffer(empty_sv); BOOST_TEST_EQ(b.data(), nullptr); BOOST_TEST_EQ(b.size(), 0u); @@ -413,6 +477,7 @@ struct make_buffer_test // Wide string_view { std::wstring_view wsv = L"hello"; + CAPY_ASSERT_RETURNS(const_buffer, wsv); const_buffer b = make_buffer(wsv); BOOST_TEST_EQ(b.data(), wsv.data()); BOOST_TEST_EQ(b.size(), 5u * sizeof(wchar_t)); @@ -428,14 +493,26 @@ struct make_buffer_test // make_buffer(std::span) - mutable { std::span sp(buf); + CAPY_ASSERT_RETURNS(mutable_buffer, sp); mutable_buffer b = make_buffer(sp); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 10u); } + // make_buffer(std::span) - mutable, passed as a temporary. + // Regression: a prvalue must still yield a mutable_buffer once + // the dedicated by-value span overload is gone. + { + CAPY_ASSERT_RETURNS(mutable_buffer, std::span(buf)); + mutable_buffer b = make_buffer(std::span(buf)); + BOOST_TEST_EQ(b.data(), buf); + BOOST_TEST_EQ(b.size(), 10u); + } + // make_buffer(std::span, max_size) - no truncation { std::span sp(buf); + CAPY_ASSERT_RETURNS(mutable_buffer, sp, 20); mutable_buffer b = make_buffer(sp, 20); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 10u); @@ -444,6 +521,7 @@ struct make_buffer_test // make_buffer(std::span, max_size) - truncation { std::span sp(buf); + CAPY_ASSERT_RETURNS(mutable_buffer, sp, 5); mutable_buffer b = make_buffer(sp, 5); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 5u); @@ -452,6 +530,7 @@ struct make_buffer_test // make_buffer(std::span) - const { std::span sp(cbuf); + CAPY_ASSERT_RETURNS(const_buffer, sp); const_buffer b = make_buffer(sp); BOOST_TEST_EQ(b.data(), cbuf); BOOST_TEST_EQ(b.size(), 10u); @@ -460,6 +539,7 @@ struct make_buffer_test // make_buffer(std::span, max_size) - no truncation { std::span sp(cbuf); + CAPY_ASSERT_RETURNS(const_buffer, sp, 20); const_buffer b = make_buffer(sp, 20); BOOST_TEST_EQ(b.data(), cbuf); BOOST_TEST_EQ(b.size(), 10u); @@ -468,6 +548,7 @@ struct make_buffer_test // make_buffer(std::span, max_size) - truncation { std::span sp(cbuf); + CAPY_ASSERT_RETURNS(const_buffer, sp, 5); const_buffer b = make_buffer(sp, 5); BOOST_TEST_EQ(b.data(), cbuf); BOOST_TEST_EQ(b.size(), 5u); @@ -476,12 +557,72 @@ struct make_buffer_test // Fixed-extent span { std::span sp(buf); + CAPY_ASSERT_RETURNS(mutable_buffer, sp); mutable_buffer b = make_buffer(sp); BOOST_TEST_EQ(b.data(), buf); BOOST_TEST_EQ(b.size(), 10u); } } +#ifdef BOOST_CAPY_TEST_HAS_BOOST_SPAN + void + testBoostSpan() + { + char buf[10]{}; + char const cbuf[10]{}; + + // Regression for issue #147: make_buffer(boost::span) must + // return a mutable_buffer. boost::span has no dedicated overload, + // so it exercises the generic forwarding-reference overload. + + // boost::span passed as a temporary (the reported case) + { + CAPY_ASSERT_RETURNS(mutable_buffer, boost::span(buf)); + mutable_buffer b = make_buffer(boost::span(buf)); + BOOST_TEST_EQ(b.data(), buf); + BOOST_TEST_EQ(b.size(), 10u); + } + + // boost::span lvalue + { + boost::span sp(buf); + CAPY_ASSERT_RETURNS(mutable_buffer, sp); + mutable_buffer b = make_buffer(sp); + BOOST_TEST_EQ(b.data(), buf); + BOOST_TEST_EQ(b.size(), 10u); + } + + // boost::span with max_size - truncation + { + boost::span sp(buf); + CAPY_ASSERT_RETURNS(mutable_buffer, sp, 5); + mutable_buffer b = make_buffer(sp, 5); + BOOST_TEST_EQ(b.data(), buf); + BOOST_TEST_EQ(b.size(), 5u); + } + + // boost::span -> const_buffer + { + boost::span sp(cbuf); + CAPY_ASSERT_RETURNS(const_buffer, sp); + const_buffer b = make_buffer(sp); + BOOST_TEST_EQ(b.data(), cbuf); + BOOST_TEST_EQ(b.size(), 10u); + } + } +#endif + + void + testStringLiteral() + { + // A string literal is a const char array; it must resolve to the + // const range overload (not string_view) and include the trailing + // '\0', matching the prior dedicated C-array overload. + CAPY_ASSERT_RETURNS(const_buffer, "Hello"); + const_buffer b = make_buffer("Hello"); + BOOST_TEST_EQ(b.size(), 6u); + } + void run() { @@ -494,6 +635,10 @@ struct make_buffer_test testStdString(); testStdStringView(); testStdSpan(); +#ifdef BOOST_CAPY_TEST_HAS_BOOST_SPAN + testBoostSpan(); +#endif + testStringLiteral(); } }; @@ -503,3 +648,5 @@ TEST_SUITE( } // capy } // boost + +#undef CAPY_ASSERT_RETURNS diff --git a/test/unit/concept/buffer_source.cpp b/test/unit/concept/buffer_source.cpp index 9cab22153..7720978e1 100644 --- a/test/unit/concept/buffer_source.cpp +++ b/test/unit/concept/buffer_source.cpp @@ -15,6 +15,7 @@ #include #include +#include #include namespace boost { diff --git a/test/unit/io/any_buffer_sink.cpp b/test/unit/io/any_buffer_sink.cpp index 2e29e2fd7..8159d8513 100644 --- a/test/unit/io/any_buffer_sink.cpp +++ b/test/unit/io/any_buffer_sink.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include diff --git a/test/unit/io/any_buffer_source.cpp b/test/unit/io/any_buffer_source.cpp index 91b4696d4..f752bdb69 100644 --- a/test/unit/io/any_buffer_source.cpp +++ b/test/unit/io/any_buffer_source.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include diff --git a/test/unit/io/any_read_stream.cpp b/test/unit/io/any_read_stream.cpp index bb8705a1e..7fe13cbcb 100644 --- a/test/unit/io/any_read_stream.cpp +++ b/test/unit/io/any_read_stream.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include diff --git a/test/unit/io/any_write_stream.cpp b/test/unit/io/any_write_stream.cpp index ae298094a..484b43af4 100644 --- a/test/unit/io/any_write_stream.cpp +++ b/test/unit/io/any_write_stream.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include diff --git a/test/unit/test/buffer_sink.cpp b/test/unit/test/buffer_sink.cpp index d04f83724..de0381648 100644 --- a/test/unit/test/buffer_sink.cpp +++ b/test/unit/test/buffer_sink.cpp @@ -17,6 +17,7 @@ #include "test/unit/test_helpers.hpp" #include +#include #include namespace boost { diff --git a/test/unit/test/buffer_source.cpp b/test/unit/test/buffer_source.cpp index ba788579b..398c055cf 100644 --- a/test/unit/test/buffer_source.cpp +++ b/test/unit/test/buffer_source.cpp @@ -18,6 +18,7 @@ #include "test/unit/test_helpers.hpp" +#include #include namespace boost {