From 1ba20f10a62e5fd629d6934632ffa2e28602eea2 Mon Sep 17 00:00:00 2001 From: Can Date: Tue, 23 Jun 2026 09:57:10 +0300 Subject: [PATCH 1/2] read_until requires copyable MatchCondition despite the concept allowing move-only matchers --- include/boost/capy/read_until.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/capy/read_until.hpp b/include/boost/capy/read_until.hpp index ab9965580..4a0e39313 100644 --- a/include/boost/capy/read_until.hpp +++ b/include/boost/capy/read_until.hpp @@ -180,7 +180,7 @@ struct read_until_awaitable await_suspend(std::coroutine_handle<> h, io_env const* env) { inner_.emplace(read_until_match_impl( - *stream_, buffers(), match_, initial_amount_)); + *stream_, buffers(), std::move(match_), initial_amount_)); return inner_->await_suspend(h, env); } From c030156132d8458120ac4249683bcb5c0af982b9 Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Tue, 23 Jun 2026 22:15:23 +0200 Subject: [PATCH 2/2] test(read_until): cover move-only MatchConditions The MatchCondition concept requires only callability, not copyability, so read_until must accept move-only matchers. Add a matcher holding its delimiter in a unique_ptr and exercise it through the await_suspend I/O path, which previously copied the matcher and rejected such types. --- test/unit/read_until.cpp | 49 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/unit/read_until.cpp b/test/unit/read_until.cpp index 56e87c6a9..8c70212e8 100644 --- a/test/unit/read_until.cpp +++ b/test/unit/read_until.cpp @@ -19,9 +19,11 @@ #include "test_suite.hpp" #include +#include #include #include #include +#include namespace boost { namespace capy { @@ -451,6 +453,52 @@ struct read_until_test })); } + void + testMoveOnlyMatcher() + { + struct move_only_matcher + { + std::unique_ptr delim; + + explicit move_only_matcher(std::string d) + : delim(std::make_unique(std::move(d))) + { + } + + std::size_t + operator()( + std::string_view data, + std::size_t* hint) const + { + auto pos = data.find(*delim); + if(pos != std::string_view::npos) + return pos + delim->size(); + if(hint) + *hint = delim->size() > 1 ? delim->size() - 1 : 0; + return std::string_view::npos; + } + }; + + static_assert(MatchCondition); + static_assert(! std::is_copy_constructible_v); + + // Chunked reads route the matcher through await_suspend, which + // moves rather than copies it. + BOOST_TEST(test::fuse().armed([](test::fuse& f) -> task + { + test::read_stream rs(f, 4); + rs.provide("hello\r\nworld"); + + std::string data; + auto [ec, n] = co_await read_until( + rs, dynamic_buffer(data), move_only_matcher("\r\n")); + if(ec) + co_return; + + BOOST_TEST_EQ(n, 7u); // "hello\r\n" + })); + } + //---------------------------------------------------------- void @@ -486,6 +534,7 @@ struct read_until_test testErrorConditions(); testPrefilledBuffer(); testMatchCondition(); + testMoveOnlyMatcher(); testMatchHelpers(); } };