From 836f2c58fdb3f4b5f1606e9944180c8f34b5ebe6 Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Mon, 17 Aug 2026 18:00:53 +0200 Subject: [PATCH] refactor!: make io_result an alias for std::tuple A distinct result type cannot opt into the standard tuple utilities: std::apply is specified in terms of std::get, which cannot be overloaded for program-defined types, and tuple's operator= only accepts tuple-like types, so std::tie can never rebind from one. Define io_result as std::tuple instead, making tie, apply, tuple_cat, comparisons, and tuple assignment all work (closes #266). tie in particular gives callers a rebinding style that avoids structured bindings entirely. Outcome detection for the io-aware when_all/when_any overloads is now structural, matching the channel-splitting rule the sender bridge already uses: any tuple whose first element is error_code participates, regardless of spelling. The type-level [[nodiscard]] migrates to await_resume on the library's awaitables; task gains a nodiscard overload when T is an outcome, so discarding an awaited error still warns. Two construction idioms had to change. extract_results built its container with CTAD, which now collapses a single child through tuple's copy deduction guide; the element types are spelled out. And the {{}, n} success shorthand is ambiguous in libstdc++, where the leading {} also matches allocator_arg_t in the allocator-extended constructors; success results now spell std::error_code() explicitly. --- bench/beman/ioaw_read_stream.hpp | 2 +- bench/beman/ioaw_sync_read_stream.hpp | 2 +- bench/beman/sender_awaitable.hpp | 4 +- bench/stdexec/ioaw_read_stream.hpp | 2 +- bench/stdexec/ioaw_sync_read_stream.hpp | 2 +- bench/stdexec/sender_awaitable.hpp | 4 +- .../pages/4.coroutines/4f.composition.adoc | 2 +- .../pages/8.examples/8g.parallel-fetch.adoc | 2 +- .../pages/9.design/9i.TypeEraseAwaitable.adoc | 4 +- .../Ac.contingencies.adoc | 2 +- doc/unlisted/coroutines-when-all.adoc | 2 +- doc/unlisted/coroutines-when-any.adoc | 8 +- doc/unlisted/library-io-result.adoc | 27 +++- example/asio/any_stream.cpp | 4 +- example/asio/api/capy_streams.cpp | 8 +- example/asio/api/use_capy.hpp | 20 +-- example/asio/use_capy_example.cpp | 4 +- example/async-mutex/async_mutex.cpp | 4 +- .../cuda/datamovement/cuda_datamovement.hpp | 2 +- example/cuda/pipeline/sender_awaitable.hpp | 4 +- example/custom-executor/custom_executor.cpp | 2 +- example/parallel-fetch/parallel_fetch.cpp | 6 +- example/parallel-tasks/parallel_tasks.cpp | 2 +- example/sender-bridge/sender_awaitable.hpp | 4 +- .../timeout_cancellation.cpp | 2 +- .../when_any_cancellation.cpp | 4 +- .../capy/detail/io_result_combinators.hpp | 26 ++-- include/boost/capy/ex/async_event.hpp | 2 +- include/boost/capy/ex/async_mutex.hpp | 10 +- include/boost/capy/ex/async_waker.hpp | 2 +- include/boost/capy/ex/immediate.hpp | 18 +-- include/boost/capy/io/any_read_stream.hpp | 2 +- include/boost/capy/io/any_write_stream.hpp | 4 +- include/boost/capy/io/write_now.hpp | 32 ++--- include/boost/capy/io_result.hpp | 128 +++++------------ include/boost/capy/io_task.hpp | 4 +- include/boost/capy/read.hpp | 2 +- include/boost/capy/read_at_least.hpp | 2 +- include/boost/capy/task.hpp | 13 ++ include/boost/capy/test/read_stream.hpp | 6 +- include/boost/capy/test/stream.hpp | 12 +- include/boost/capy/test/write_stream.hpp | 6 +- include/boost/capy/when_all.hpp | 16 ++- include/boost/capy/when_any.hpp | 8 +- include/boost/capy/write.hpp | 2 +- include/boost/capy/write_at_least.hpp | 2 +- .../programs/8m_parallel_tasks_variadic.cpp | 2 +- test/doc/snippets/4e_cancellation.cpp | 2 +- test/doc/snippets/4f_composition.cpp | 34 ++--- test/unit/ex/immediate.cpp | 48 +++---- test/unit/io/any_read_stream.cpp | 4 +- test/unit/io/any_stream.cpp | 6 +- test/unit/io/any_write_stream.cpp | 2 +- test/unit/io/write_now.cpp | 2 +- test/unit/io_result.cpp | 134 ++++++++++++++---- test/unit/quitter.cpp | 2 +- test/unit/when_all.cpp | 120 ++++++++-------- test/unit/when_any.cpp | 19 +-- 58 files changed, 426 insertions(+), 375 deletions(-) diff --git a/bench/beman/ioaw_read_stream.hpp b/bench/beman/ioaw_read_stream.hpp index 5eb0dd4f8..11f6ce30c 100644 --- a/bench/beman/ioaw_read_stream.hpp +++ b/bench/beman/ioaw_read_stream.hpp @@ -42,7 +42,7 @@ struct ioaw_read_stream } boost::capy::io_result - await_resume() noexcept { return {{}, 0}; } + await_resume() noexcept { return {std::error_code(), 0}; } }; template diff --git a/bench/beman/ioaw_sync_read_stream.hpp b/bench/beman/ioaw_sync_read_stream.hpp index cdb68c1fc..0a75aeef1 100644 --- a/bench/beman/ioaw_sync_read_stream.hpp +++ b/bench/beman/ioaw_sync_read_stream.hpp @@ -46,7 +46,7 @@ struct ioaw_sync_read_stream boost::capy::io_result await_resume() noexcept { - return {{}, 0}; + return {std::error_code(), 0}; } }; diff --git a/bench/beman/sender_awaitable.hpp b/bench/beman/sender_awaitable.hpp index edcf4a6b3..94abb85e5 100644 --- a/bench/beman/sender_awaitable.hpp +++ b/bench/beman/sender_awaitable.hpp @@ -342,7 +342,7 @@ struct [[nodiscard]] sender_awaitable return io_result{ std::get<2>(result_)}; return io_result{ - {}, + std::error_code(), std::get<0>( std::get<1>( std::move(result_)))}; @@ -357,7 +357,7 @@ struct [[nodiscard]] sender_awaitable return io_result{ std::get<2>(result_)}; return io_result{ - {}, + std::error_code(), std::get<1>( std::move(result_))}; } diff --git a/bench/stdexec/ioaw_read_stream.hpp b/bench/stdexec/ioaw_read_stream.hpp index 71c67f4b1..cd76367d9 100644 --- a/bench/stdexec/ioaw_read_stream.hpp +++ b/bench/stdexec/ioaw_read_stream.hpp @@ -42,7 +42,7 @@ struct ioaw_read_stream } boost::capy::io_result - await_resume() noexcept { return {{}, 0}; } + await_resume() noexcept { return {std::error_code(), 0}; } }; template diff --git a/bench/stdexec/ioaw_sync_read_stream.hpp b/bench/stdexec/ioaw_sync_read_stream.hpp index 5daaac128..84f2d16aa 100644 --- a/bench/stdexec/ioaw_sync_read_stream.hpp +++ b/bench/stdexec/ioaw_sync_read_stream.hpp @@ -46,7 +46,7 @@ struct ioaw_sync_read_stream boost::capy::io_result await_resume() noexcept { - return {{}, 0}; + return {std::error_code(), 0}; } }; diff --git a/bench/stdexec/sender_awaitable.hpp b/bench/stdexec/sender_awaitable.hpp index 045d2b893..16c3bb9d8 100644 --- a/bench/stdexec/sender_awaitable.hpp +++ b/bench/stdexec/sender_awaitable.hpp @@ -341,7 +341,7 @@ struct [[nodiscard]] sender_awaitable return io_result{ std::get<2>(result_), T{}}; return io_result{ - {}, + std::error_code(), std::get<0>( std::get<1>( std::move(result_)))}; @@ -356,7 +356,7 @@ struct [[nodiscard]] sender_awaitable return io_result{ std::get<2>(result_), value_tuple{}}; return io_result{ - {}, + std::error_code(), std::get<1>( std::move(result_))}; } diff --git a/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc b/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc index b104b4078..f2dc78223 100644 --- a/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc +++ b/doc/modules/ROOT/pages/4.coroutines/4f.composition.adoc @@ -45,7 +45,7 @@ include::example$snippets/4f_composition.cpp[tag=when_all_basic,indent=0] include::example$snippets/4f_composition.cpp[tag=when_all_void_mix,indent=0] ---- -When all children are `io_task<>`, just check `r.ec`: +When all children are `io_task<>`, just check the error code: [source,cpp] ---- diff --git a/doc/modules/ROOT/pages/8.examples/8g.parallel-fetch.adoc b/doc/modules/ROOT/pages/8.examples/8g.parallel-fetch.adoc index 02dcda3e4..b0d82653d 100644 --- a/doc/modules/ROOT/pages/8.examples/8g.parallel-fetch.adoc +++ b/doc/modules/ROOT/pages/8.examples/8g.parallel-fetch.adoc @@ -46,7 +46,7 @@ include::example$parallel-fetch/parallel_fetch.cpp[tag=when_all_dashboard,indent include::example$parallel-fetch/parallel_fetch.cpp[tag=when_all_void,indent=0] ---- -`io_task<>` children return `io_result<>` (just an error code, no payload). Check `r.ec` to detect failure. +`io_task<>` children return `io_result<>` (just an error code, no payload). Check `std::get<0>(r)` to detect failure. === Error Propagation diff --git a/doc/modules/ROOT/pages/9.design/9i.TypeEraseAwaitable.adoc b/doc/modules/ROOT/pages/9.design/9i.TypeEraseAwaitable.adoc index 2d1a25252..3e18922dd 100644 --- a/doc/modules/ROOT/pages/9.design/9i.TypeEraseAwaitable.adoc +++ b/doc/modules/ROOT/pages/9.design/9i.TypeEraseAwaitable.adoc @@ -72,7 +72,7 @@ std::coroutine_handle<> await_suspend(std::coroutine_handle<> h, io_env const* e io_result await_resume() { if(!awaitable_active_) - return {{}, 0}; // short-circuited + return {std::error_code(), 0}; // short-circuited auto r = vt_->await_resume(cached_awaitable_); vt_->destroy_awaitable(cached_awaitable_); awaitable_active_ = false; @@ -130,7 +130,7 @@ std::coroutine_handle<> await_suspend(std::coroutine_handle<> h, io_env const* e io_result await_resume() { if(!active_ops_) - return {{}, 0}; // short-circuited + return {std::error_code(), 0}; // short-circuited auto r = active_ops_->await_resume(cached_awaitable_); active_ops_->destroy(cached_awaitable_); active_ops_ = nullptr; diff --git a/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc b/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc index 54ccc9caa..6ee2f2e25 100644 --- a/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc +++ b/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc @@ -18,7 +18,7 @@ as their postconditions never say that the requested number of bytes will indeed processed. Each stream operation that may encounter a contingency await-returns -a type which is a specialization of `capy::io_result`. These objects can be _destructured_ +an instance of `capy::io_result`. These objects can be _destructured_ using a structured binding. The first binding of such destructuring is of type `std::error_code`. This binding, call it `ec`, is used to signal if and which contingency occured: diff --git a/doc/unlisted/coroutines-when-all.adoc b/doc/unlisted/coroutines-when-all.adoc index e58644e55..409aa849e 100644 --- a/doc/unlisted/coroutines-when-all.adoc +++ b/doc/unlisted/coroutines-when-all.adoc @@ -83,7 +83,7 @@ auto r = co_await when_all( task_void(), task_void() ); -if (r.ec) +if (std::get<0>(r)) // handle error ---- diff --git a/doc/unlisted/coroutines-when-any.adoc b/doc/unlisted/coroutines-when-any.adoc index eb21ca7c8..7d55ba3c4 100644 --- a/doc/unlisted/coroutines-when-any.adoc +++ b/doc/unlisted/coroutines-when-any.adoc @@ -233,10 +233,10 @@ io_task fetch_with_cancel_support() for (auto& chunk : data_source) { if (token.stop_requested()) - co_return io_result{{}, partial_response()}; + co_return io_result{std::error_code(), partial_response()}; co_await send_chunk(chunk); } - co_return io_result{{}, complete_response()}; + co_return io_result{std::error_code(), complete_response()}; } task<> example() @@ -333,7 +333,7 @@ io_task fetch_with_redundancy(Request req) if constexpr (!std::is_same_v, std::error_code>) resp = v; }, result); - co_return io_result{{}, std::move(resp)}; + co_return io_result{std::error_code(), std::move(resp)}; } ---- @@ -357,7 +357,7 @@ io_task get_connection(std::vector& pools) auto& [index, conn] = std::get<1>(result); std::cout << "Got connection from pool " << index << "\n"; - co_return io_result{{}, std::move(conn)}; + co_return io_result{std::error_code(), std::move(conn)}; } ---- diff --git a/doc/unlisted/library-io-result.adoc b/doc/unlisted/library-io-result.adoc index 423263190..bee5be075 100644 --- a/doc/unlisted/library-io-result.adoc +++ b/doc/unlisted/library-io-result.adoc @@ -62,7 +62,30 @@ Features: * Structured bindings for clean syntax * Error code is always present * Additional values (bytes transferred, etc.) included -* `[[nodiscard]]` prevents ignoring results +* An alias for `std::tuple`, so the whole standard tuple API + applies: `std::tie`, `std::apply`, `std::get`, comparisons +* Library awaitables mark `await_resume` `[[nodiscard]]`, so + discarding a result is diagnosed + +== Rebinding with std::tie + +When a coroutine performs several operations in sequence, +`std::tie` reassigns into existing variables instead of +introducing a new binding per operation: + +[source,cpp] +---- +std::error_code ec; +std::size_t n = 0; + +std::tie(ec, n) = co_await s.read_some(buf); +if (ec) + co_return ec; + +std::tie(ec, n) = co_await s.write(buf); +if (ec) + co_return ec; +---- == io_result Variants @@ -185,7 +208,7 @@ io_task read_all(stream& s, buffer& buf) buf.commit(n); total += n; } - co_return {{}, total}; // Success with total bytes + co_return {std::error_code(), total}; // Success with total bytes } ---- diff --git a/example/asio/any_stream.cpp b/example/asio/any_stream.cpp index 9955e6a56..7165cdcc9 100644 --- a/example/asio/any_stream.cpp +++ b/example/asio/any_stream.cpp @@ -82,8 +82,8 @@ run_example( writer(client_stream, total_bytes), reader(server_stream, total_bytes)); - if(r.ec) - std::printf("example error: %s\n", r.ec.message().c_str()); + if(std::get<0>(r)) + std::printf("example error: %s\n", std::get<0>(r).message().c_str()); else std::printf("example complete!\n"); } diff --git a/example/asio/api/capy_streams.cpp b/example/asio/api/capy_streams.cpp index af35f7808..85af240c3 100644 --- a/example/asio/api/capy_streams.cpp +++ b/example/asio/api/capy_streams.cpp @@ -114,8 +114,8 @@ class asio_socket boost::system::error_code ec, std::size_t n) mutable { - result_.ec = ec; - std::get<0>(result_.values) = n; + std::get<0>(result_) = ec; + std::get<1>(result_) = n; ex.post(cont_); })); @@ -178,8 +178,8 @@ class asio_socket boost::system::error_code ec, std::size_t n) mutable { - result_.ec = ec; - std::get<0>(result_.values) = n; + std::get<0>(result_) = ec; + std::get<1>(result_) = n; ex.post(cont_); })); diff --git a/example/asio/api/use_capy.hpp b/example/asio/api/use_capy.hpp index 2c13e1e8a..f0cbc2348 100644 --- a/example/asio/api/use_capy.hpp +++ b/example/asio/api/use_capy.hpp @@ -162,31 +162,31 @@ class capy_awaitable private: void store_result(boost::system::error_code ec) { - result_.ec = ec; + std::get<0>(result_) = ec; } template void store_result(boost::system::error_code ec, T1&& t1) { - result_.ec = ec; - std::get<0>(result_.values) = std::forward(t1); + std::get<0>(result_) = ec; + std::get<1>(result_) = std::forward(t1); } template void store_result(boost::system::error_code ec, T1&& t1, T2&& t2) { - result_.ec = ec; - std::get<0>(result_.values) = std::forward(t1); - std::get<1>(result_.values) = std::forward(t2); + std::get<0>(result_) = ec; + std::get<1>(result_) = std::forward(t1); + std::get<2>(result_) = std::forward(t2); } template void store_result(boost::system::error_code ec, T1&& t1, T2&& t2, T3&& t3) { - result_.ec = ec; - std::get<0>(result_.values) = std::forward(t1); - std::get<1>(result_.values) = std::forward(t2); - std::get<2>(result_.values) = std::forward(t3); + std::get<0>(result_) = ec; + std::get<1>(result_) = std::forward(t1); + std::get<2>(result_) = std::forward(t2); + std::get<3>(result_) = std::forward(t3); } }; diff --git a/example/asio/use_capy_example.cpp b/example/asio/use_capy_example.cpp index 83772cd01..17aee661b 100644 --- a/example/asio/use_capy_example.cpp +++ b/example/asio/use_capy_example.cpp @@ -95,8 +95,8 @@ run_example( writer(client, total_bytes), reader(server, total_bytes)); - if (r.ec) - std::printf("example error: %s\n", r.ec.message().c_str()); + if (std::get<0>(r)) + std::printf("example error: %s\n", std::get<0>(r).message().c_str()); else std::printf("example complete!\n"); } diff --git a/example/async-mutex/async_mutex.cpp b/example/async-mutex/async_mutex.cpp index 71f9b34e9..654263930 100644 --- a/example/async-mutex/async_mutex.cpp +++ b/example/async-mutex/async_mutex.cpp @@ -72,9 +72,9 @@ int main() auto r = co_await capy::when_all( worker(0), worker(1), worker(2), worker(3), worker(4), worker(5)); - if(r.ec) + if(std::get<0>(r)) std::cerr << "when_all error: " - << r.ec.message() << "\n"; + << std::get<0>(r).message() << "\n"; }; // Run on a strand so async_mutex operations are single-threaded diff --git a/example/cuda/datamovement/cuda_datamovement.hpp b/example/cuda/datamovement/cuda_datamovement.hpp index 4855cd675..165bbc260 100644 --- a/example/cuda/datamovement/cuda_datamovement.hpp +++ b/example/cuda/datamovement/cuda_datamovement.hpp @@ -339,7 +339,7 @@ class cuda_device_stream } auto n = buf.size(); self->offset_ += n; - return {{}, n}; + return {std::error_code(), n}; } }; return awaitable{this, *capy::begin(buffers)}; diff --git a/example/cuda/pipeline/sender_awaitable.hpp b/example/cuda/pipeline/sender_awaitable.hpp index c27ac505d..c28f5c546 100644 --- a/example/cuda/pipeline/sender_awaitable.hpp +++ b/example/cuda/pipeline/sender_awaitable.hpp @@ -342,7 +342,7 @@ struct [[nodiscard]] sender_awaitable return io_result{ std::get<2>(result_), T{}}; return io_result{ - {}, + std::error_code(), std::get<0>( std::get<1>( std::move(result_)))}; @@ -357,7 +357,7 @@ struct [[nodiscard]] sender_awaitable return io_result{ std::get<2>(result_), value_tuple{}}; return io_result{ - {}, + std::error_code(), std::get<1>( std::move(result_))}; } diff --git a/example/custom-executor/custom_executor.cpp b/example/custom-executor/custom_executor.cpp index 07d55e57a..c03c160f3 100644 --- a/example/custom-executor/custom_executor.cpp +++ b/example/custom-executor/custom_executor.cpp @@ -138,7 +138,7 @@ static_assert(capy::Executor); capy::io_task compute(int x) { std::cout << " computing " << x << " * " << x << "\n"; - co_return capy::io_result{{}, x * x}; + co_return capy::io_result{std::error_code(), x * x}; } capy::task<> run_tasks() diff --git a/example/parallel-fetch/parallel_fetch.cpp b/example/parallel-fetch/parallel_fetch.cpp index 2be734fbe..c643b62f9 100644 --- a/example/parallel-fetch/parallel_fetch.cpp +++ b/example/parallel-fetch/parallel_fetch.cpp @@ -57,7 +57,7 @@ capy::task<> fetch_user_dashboard(std::string username) auto wrap = [](auto inner) -> capy::io_task { co_return capy::io_result{ - {}, co_await std::move(inner)}; + std::error_code(), co_await std::move(inner)}; }; // tag::when_all_dashboard[] @@ -94,7 +94,7 @@ capy::task fetch_with_side_effects() auto r = co_await capy::when_all( log_access("api/data"), update_metrics("api_calls")); - if (r.ec) + if (std::get<0>(r)) co_return "error"; // end::when_all_void[] @@ -115,7 +115,7 @@ capy::io_task might_fail(bool should_fail, std::string name) } std::cout << "Task " << name << " completed\n"; - co_return capy::io_result{{}, 42}; + co_return capy::io_result{std::error_code(), 42}; } capy::task<> demonstrate_error_handling() diff --git a/example/parallel-tasks/parallel_tasks.cpp b/example/parallel-tasks/parallel_tasks.cpp index 0765d97d3..50f0e889b 100644 --- a/example/parallel-tasks/parallel_tasks.cpp +++ b/example/parallel-tasks/parallel_tasks.cpp @@ -35,7 +35,7 @@ capy::io_task partial_sum(int lo, int hi) long long sum = 0; for (int i = lo; i < hi; ++i) sum += i; - co_return capy::io_result{{}, sum}; + co_return capy::io_result{std::error_code(), sum}; } int main() diff --git a/example/sender-bridge/sender_awaitable.hpp b/example/sender-bridge/sender_awaitable.hpp index a18ac2deb..34d32853f 100644 --- a/example/sender-bridge/sender_awaitable.hpp +++ b/example/sender-bridge/sender_awaitable.hpp @@ -320,7 +320,7 @@ struct [[nodiscard]] sender_awaitable return io_result{ std::get<2>(result_)}; return io_result{ - {}, + std::error_code(), std::get<0>( std::get<1>( std::move(result_)))}; @@ -335,7 +335,7 @@ struct [[nodiscard]] sender_awaitable return io_result{ std::get<2>(result_)}; return io_result{ - {}, + std::error_code(), std::get<1>( std::move(result_))}; } diff --git a/example/timeout-cancellation/timeout_cancellation.cpp b/example/timeout-cancellation/timeout_cancellation.cpp index 99cd96f49..7ab8d4373 100644 --- a/example/timeout-cancellation/timeout_cancellation.cpp +++ b/example/timeout-cancellation/timeout_cancellation.cpp @@ -78,7 +78,7 @@ capy::io_task await_fetch(fetch_channel& ch) ch.cancelled.store(true); co_return capy::io_result{ec, {}}; } - co_return capy::io_result{{}, std::move(ch.result)}; + co_return capy::io_result{std::error_code(), std::move(ch.result)}; } // end::race_await_fetch[] diff --git a/example/when-any-cancellation/when_any_cancellation.cpp b/example/when-any-cancellation/when_any_cancellation.cpp index 020e45f92..98bf00680 100644 --- a/example/when-any-cancellation/when_any_cancellation.cpp +++ b/example/when-any-cancellation/when_any_cancellation.cpp @@ -48,7 +48,7 @@ capy::io_task fetch_from_source( { std::cout << " [" << name << "] cancelled at step " << i << "/" << steps << "\n"; - co_return capy::io_result{{}, name + ": cancelled"}; + co_return capy::io_result{std::error_code(), name + ": cancelled"}; } // Simulate work @@ -59,7 +59,7 @@ capy::io_task fetch_from_source( << (i + 1) << "/" << steps << "\n"; } - co_return capy::io_result{{}, name + ": done"}; + co_return capy::io_result{std::error_code(), name + ": done"}; } // Race three sources — the fastest one wins, the rest get cancelled. diff --git a/include/boost/capy/detail/io_result_combinators.hpp b/include/boost/capy/detail/io_result_combinators.hpp index fb7dc3aa8..bd92ce82a 100644 --- a/include/boost/capy/detail/io_result_combinators.hpp +++ b/include/boost/capy/detail/io_result_combinators.hpp @@ -22,15 +22,6 @@ namespace boost { namespace capy { namespace detail { -template -struct is_io_result : std::false_type {}; - -template -struct is_io_result> : std::true_type {}; - -template -inline constexpr bool is_io_result_v = is_io_result::value; - /// True when every awaitable in the pack returns an io_result. template concept all_io_result_awaitables = @@ -77,14 +68,16 @@ template T extract_io_payload(io_result&& r) { - return std::get<0>(std::move(r.values)); + return std::get<1>(std::move(r)); } template std::tuple extract_io_payload(io_result&& r) { - return std::move(r.values); + return [&](std::index_sequence) { + return std::tuple(std::get(std::move(r))...); + }(std::index_sequence_for{}); } /// Reconstruct a success io_result from a payload extracted by when_any. @@ -96,7 +89,7 @@ struct io_result_from_payload> { static io_result apply(T t) { - return io_result{{}, std::move(t)}; + return io_result{std::error_code(), std::move(t)}; } }; @@ -105,9 +98,8 @@ struct io_result_from_payload> { static io_result apply(std::tuple t) { - return std::apply([](auto&&... args) { - return io_result{{}, std::move(args)...}; - }, std::move(t)); + return std::tuple_cat( + std::tuple(), std::move(t)); } }; @@ -117,8 +109,8 @@ ResultType build_when_all_io_result_impl(Tuple&& results, std::index_sequence) { std::error_code ec; - (void)((std::get(results).ec && !ec - ? (ec = std::get(results).ec, true) + (void)((std::get<0>(std::get(results)) && !ec + ? (ec = std::get<0>(std::get(results)), true) : false) || ...); return ResultType{ec, extract_io_payload( diff --git a/include/boost/capy/ex/async_event.hpp b/include/boost/capy/ex/async_event.hpp index ed254b2be..00258c00b 100644 --- a/include/boost/capy/ex/async_event.hpp +++ b/include/boost/capy/ex/async_event.hpp @@ -214,7 +214,7 @@ class async_event return std::noop_coroutine(); } - io_result<> await_resume() noexcept + [[nodiscard]] io_result<> await_resume() noexcept { if(active_) { diff --git a/include/boost/capy/ex/async_mutex.hpp b/include/boost/capy/ex/async_mutex.hpp index 90c9a4a13..bf6b275db 100644 --- a/include/boost/capy/ex/async_mutex.hpp +++ b/include/boost/capy/ex/async_mutex.hpp @@ -267,7 +267,7 @@ class async_mutex return std::noop_coroutine(); } - io_result<> await_resume() noexcept + [[nodiscard]] io_result<> await_resume() noexcept { if(active_) { @@ -361,12 +361,12 @@ class async_mutex return inner_.await_suspend(h, env); } - io_result await_resume() noexcept + [[nodiscard]] io_result await_resume() noexcept { auto r = inner_.await_resume(); - if(r.ec) - return {r.ec, {}}; - return {{}, lock_guard(m_)}; + if(std::get<0>(r)) + return {std::get<0>(r), lock_guard()}; + return {std::error_code(), lock_guard(m_)}; } }; diff --git a/include/boost/capy/ex/async_waker.hpp b/include/boost/capy/ex/async_waker.hpp index 3f47af66a..01232c772 100644 --- a/include/boost/capy/ex/async_waker.hpp +++ b/include/boost/capy/ex/async_waker.hpp @@ -266,7 +266,7 @@ class async_waker return std::noop_coroutine(); } - io_result<> await_resume() noexcept + [[nodiscard]] io_result<> await_resume() noexcept { if(active_) { diff --git a/include/boost/capy/ex/immediate.hpp b/include/boost/capy/ex/immediate.hpp index 7f315a96a..316783c29 100644 --- a/include/boost/capy/ex/immediate.hpp +++ b/include/boost/capy/ex/immediate.hpp @@ -56,7 +56,7 @@ namespace capy { write(CB buffers) { auto n = process_sync(buffers); - return {{{}, n}}; + return {{std::error_code(), n}}; } immediate> @@ -107,14 +107,14 @@ struct immediate @return The stored value, moved if non-const. */ - constexpr T + [[nodiscard]] constexpr T await_resume() noexcept { return std::move(value_); } /** Returns the wrapped value (const overload). */ - constexpr T const& + [[nodiscard]] constexpr T const& await_resume() const noexcept { return value_; @@ -158,13 +158,13 @@ ready() noexcept @param t1 The result value. - @return An immediate awaitable containing `io_result{{}, t1}`. + @return An immediate awaitable containing `io_result{std::error_code(), t1}`. */ template immediate> ready(T1 t1) { - return {{{}, std::move(t1)}}; + return {{std::error_code(), std::move(t1)}}; } /** Create an immediate awaitable for a successful io_result with two values. @@ -172,13 +172,13 @@ ready(T1 t1) @param t1 The first result value. @param t2 The second result value. - @return An immediate awaitable containing `io_result{{}, t1, t2}`. + @return An immediate awaitable containing `io_result{std::error_code(), t1, t2}`. */ template immediate> ready(T1 t1, T2 t2) { - return {{{}, std::move(t1), std::move(t2)}}; + return {{std::error_code(), std::move(t1), std::move(t2)}}; } /** Create an immediate awaitable for a successful io_result with three values. @@ -187,13 +187,13 @@ ready(T1 t1, T2 t2) @param t2 The second result value. @param t3 The third result value. - @return An immediate awaitable containing `io_result{{}, t1, t2, t3}`. + @return An immediate awaitable containing `io_result{std::error_code(), t1, t2, t3}`. */ template immediate> ready(T1 t1, T2 t2, T3 t3) { - return {{{}, std::move(t1), std::move(t2), std::move(t3)}}; + return {{std::error_code(), std::move(t1), std::move(t2), std::move(t3)}}; } /** Create an immediate awaitable for a failed io_result. diff --git a/include/boost/capy/io/any_read_stream.hpp b/include/boost/capy/io/any_read_stream.hpp index 83f8a6e92..2b5355c72 100644 --- a/include/boost/capy/io/any_read_stream.hpp +++ b/include/boost/capy/io/any_read_stream.hpp @@ -405,7 +405,7 @@ any_read_stream::read_some(MB buffers) self_->cached_awaitable_, h, env); } - io_result + [[nodiscard]] io_result await_resume() { struct guard { diff --git a/include/boost/capy/io/any_write_stream.hpp b/include/boost/capy/io/any_write_stream.hpp index a300c2d66..2d69ce108 100644 --- a/include/boost/capy/io/any_write_stream.hpp +++ b/include/boost/capy/io/any_write_stream.hpp @@ -412,11 +412,11 @@ any_write_stream::write_some(CB buffers) self_->cached_awaitable_, h, env); } - io_result + [[nodiscard]] io_result await_resume() { if(!self_->awaitable_active_) - return {{}, 0}; + return {std::error_code(), 0}; struct guard { any_write_stream* self; ~guard() { diff --git a/include/boost/capy/io/write_now.hpp b/include/boost/capy/io/write_now.hpp index 1c1a585e5..f45b05b75 100644 --- a/include/boost/capy/io/write_now.hpp +++ b/include/boost/capy/io/write_now.hpp @@ -247,7 +247,7 @@ class write_now return h_; } - io_result await_resume() + [[nodiscard]] io_result await_resume() { auto& p = h_.promise(); if(p.ep_) @@ -339,14 +339,14 @@ class write_now { auto r = co_await stream_.write_some(cb.data()); - cb.consume(std::get<0>(r.values)); - total_written += std::get<0>(r.values); - if(r.ec) + cb.consume(std::get<1>(r)); + total_written += std::get<1>(r); + if(std::get<0>(r)) co_return io_result{ - r.ec, total_written}; + std::get<0>(r), total_written}; } co_return io_result{ - {}, total_written}; + std::error_code(), total_written}; } #else template @@ -366,16 +366,16 @@ class write_now if(!inner.await_ready()) break; auto r = inner.await_resume(); - if(r.ec) + if(std::get<0>(r)) co_return io_result{ - r.ec, total_written}; - cb.consume(std::get<0>(r.values)); - total_written += std::get<0>(r.values); + std::get<0>(r), total_written}; + cb.consume(std::get<1>(r)); + total_written += std::get<1>(r); } if(total_written >= total_size) co_return io_result{ - {}, total_written}; + std::error_code(), total_written}; co_yield 0; @@ -383,14 +383,14 @@ class write_now { auto r = co_await stream_.write_some(cb.data()); - cb.consume(std::get<0>(r.values)); - total_written += std::get<0>(r.values); - if(r.ec) + cb.consume(std::get<1>(r)); + total_written += std::get<1>(r); + if(std::get<0>(r)) co_return io_result{ - r.ec, total_written}; + std::get<0>(r), total_written}; } co_return io_result{ - {}, total_written}; + std::error_code(), total_written}; } #endif diff --git a/include/boost/capy/io_result.hpp b/include/boost/capy/io_result.hpp index 8a058ae22..b8f42aa8b 100644 --- a/include/boost/capy/io_result.hpp +++ b/include/boost/capy/io_result.hpp @@ -13,19 +13,20 @@ #include #include -#include #include #include -#include namespace boost { namespace capy { /** Result type for asynchronous I/O operations. - This template provides a unified result type for async operations, + This alias provides a unified result type for async operations, always containing a `std::error_code` plus optional additional - values. It supports structured bindings via the tuple protocol. + values. Because it is a `std::tuple`, results interoperate with + the entire standard tuple API: structured bindings, `std::tie`, + `std::apply`, `std::get`, `std::tuple_cat`, comparisons, and + tuple assignment. @par Example @code @@ -33,104 +34,45 @@ namespace capy { if (ec) { ... } @endcode - @note Whether the payload is meaningful when `ec` is set is - defined by the operation that produced the result. Many I/O - operations report a meaningful partial result alongside `ec` - (for example, the number of bytes transferred before the - condition, as with EOF); others leave it unspecified. + `std::tie` rebinds into existing variables without introducing + new bindings: + @code + std::error_code ec; + std::size_t n = 0; + std::tie(ec, n) = co_await s.read_some(buf); + @endcode + + @note Whether the payload is meaningful when the error code is + set is defined by the operation that produced the result. + Many I/O operations report a meaningful partial result + alongside the error (for example, the number of bytes + transferred before the condition, as with EOF); others + leave it unspecified. @tparam Ts Ordered payload types following the leading `std::error_code`. */ template -struct [[nodiscard]] io_result -{ - /// The error code from the operation. - std::error_code ec; +using io_result = std::tuple; - /// The payload values. Their meaning when `ec` is set is defined - /// by the producing operation (see the class note). - std::tuple values; - - /// Construct a default io_result. - io_result() = default; - - /// Construct from an error code and payload values. - io_result(std::error_code ec_, Ts... ts) - : ec(ec_) - , values(std::move(ts)...) - { - } - - /// @cond - template - decltype(auto) get() & noexcept - { - static_assert(I < 1 + sizeof...(Ts), "index out of range"); - if constexpr (I == 0) return (ec); - else return std::get(values); - } - - template - decltype(auto) get() const& noexcept - { - static_assert(I < 1 + sizeof...(Ts), "index out of range"); - if constexpr (I == 0) return (ec); - else return std::get(values); - } - - template - decltype(auto) get() && noexcept - { - static_assert(I < 1 + sizeof...(Ts), "index out of range"); - if constexpr (I == 0) return std::move(ec); - else return std::get(std::move(values)); - } - /// @endcond -}; - -/// @cond -template -decltype(auto) get(io_result& r) noexcept -{ - return r.template get(); -} - -template -decltype(auto) get(io_result const& r) noexcept -{ - return r.template get(); -} - -template -decltype(auto) get(io_result&& r) noexcept -{ - return std::move(r).template get(); -} -/// @endcond +namespace detail { -} // namespace capy -} // namespace boost - -// Tuple protocol for structured bindings -namespace std { +// Outcomes are structural, not nominal: any std::tuple whose first +// element is error_code is an io_result, regardless of how the +// producing operation spelled it. +template +struct is_io_result : std::false_type {}; template -struct tuple_size> - : std::integral_constant {}; +struct is_io_result> + : std::true_type {}; -template -struct tuple_element<0, boost::capy::io_result> -{ - using type = std::error_code; -}; - -template -struct tuple_element> -{ - using type = std::tuple_element_t>; -}; - -} // namespace std +template +inline constexpr bool is_io_result_v = is_io_result::value; + +} // namespace detail + +} // namespace capy +} // namespace boost #endif // BOOST_CAPY_IO_RESULT_HPP diff --git a/include/boost/capy/io_task.hpp b/include/boost/capy/io_task.hpp index b3067c2c2..b13f7b26d 100644 --- a/include/boost/capy/io_task.hpp +++ b/include/boost/capy/io_task.hpp @@ -19,8 +19,8 @@ namespace capy { /** A task type for I/O operations yielding io_result. This is a convenience alias for `task>`. - The converting constructor on `io_result<>` allows direct - `co_return` of error codes: + The tuple converting constructor allows direct `co_return` + of error codes: @code io_task<> connect_to_server(socket& s, endpoint ep) diff --git a/include/boost/capy/read.hpp b/include/boost/capy/read.hpp index 01839f229..35a1adec2 100644 --- a/include/boost/capy/read.hpp +++ b/include/boost/capy/read.hpp @@ -108,7 +108,7 @@ read(S& stream, MB buffers) -> co_return {ec, total_read}; } - co_return {{}, total_read}; + co_return {std::error_code(), total_read}; } } // namespace capy diff --git a/include/boost/capy/read_at_least.hpp b/include/boost/capy/read_at_least.hpp index 5362df682..dffbdf71d 100644 --- a/include/boost/capy/read_at_least.hpp +++ b/include/boost/capy/read_at_least.hpp @@ -132,7 +132,7 @@ read_at_least(S& stream, MB buffers, std::size_t n) -> co_return {ec, total_read}; } - co_return {{}, total_read}; + co_return {std::error_code(), total_read}; } } // namespace capy diff --git a/include/boost/capy/task.hpp b/include/boost/capy/task.hpp index 119ba442f..6aef9f9c7 100644 --- a/include/boost/capy/task.hpp +++ b/include/boost/capy/task.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -333,8 +334,20 @@ struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE @return The result value for non-void `T`; otherwise `void`. @throws The exception captured by the coroutine body, if any. + + @note Discarding an `io_result` silently drops the error + code, so that overload is marked `[[nodiscard]]`. */ + [[nodiscard]] auto await_resume() + requires detail::is_io_result_v + { + if(h_.promise().has_ep_) + std::rethrow_exception(h_.promise().ep_); + return std::move(*h_.promise().result_); + } + auto await_resume() + requires (! detail::is_io_result_v) { if(h_.promise().has_ep_) std::rethrow_exception(h_.promise().ep_); diff --git a/include/boost/capy/test/read_stream.hpp b/include/boost/capy/test/read_stream.hpp index 919439761..fbdce6dca 100644 --- a/include/boost/capy/test/read_stream.hpp +++ b/include/boost/capy/test/read_stream.hpp @@ -172,13 +172,13 @@ class read_stream return false; } - io_result + [[nodiscard]] io_result await_resume() { // Empty buffer is a no-op regardless of // stream state, stop token, or fuse. if(buffer_empty(buffers_)) - return {{}, 0}; + return {std::error_code(), 0}; if(canceled_) return {error::canceled, 0}; @@ -196,7 +196,7 @@ class read_stream auto src = make_buffer(self_->data_.data() + self_->pos_, avail); std::size_t const n = buffer_copy(buffers_, src); self_->pos_ += n; - return {{}, n}; + return {std::error_code(), n}; } }; return awaitable{this, buffers}; diff --git a/include/boost/capy/test/stream.hpp b/include/boost/capy/test/stream.hpp index c1a6d2c52..9bde353d9 100644 --- a/include/boost/capy/test/stream.hpp +++ b/include/boost/capy/test/stream.hpp @@ -354,7 +354,7 @@ class stream return std::noop_coroutine(); } - io_result + [[nodiscard]] io_result await_resume() { if(stop_cb_active_) @@ -364,7 +364,7 @@ class stream } if(buffer_empty(buffers_)) - return {{}, 0}; + return {std::error_code(), 0}; if(canceled_) { @@ -402,7 +402,7 @@ class stream buffers_, make_buffer(side.buf), side.max_read_size); side.buf.erase(0, n); - return {{}, n}; + return {std::error_code(), n}; } }; return awaitable{this, buffers}; @@ -456,12 +456,12 @@ class stream return false; } - io_result + [[nodiscard]] io_result await_resume() { std::size_t n = buffer_size(buffers_); if(n == 0) - return {{}, 0}; + return {std::error_code(), 0}; if(canceled_) return {error::canceled, 0}; @@ -488,7 +488,7 @@ class stream state::wake(side); - return {{}, n}; + return {std::error_code(), n}; } }; return awaitable{this, buffers}; diff --git a/include/boost/capy/test/write_stream.hpp b/include/boost/capy/test/write_stream.hpp index f5c9ccc8e..2d4769358 100644 --- a/include/boost/capy/test/write_stream.hpp +++ b/include/boost/capy/test/write_stream.hpp @@ -188,11 +188,11 @@ class write_stream return false; } - io_result + [[nodiscard]] io_result await_resume() { if(buffer_empty(buffers_)) - return {{}, 0}; + return {std::error_code(), 0}; if(canceled_) return {error::canceled, 0}; @@ -216,7 +216,7 @@ class write_stream return {ec, 0}; } - return {{}, n}; + return {std::error_code(), n}; } }; return awaitable{this, buffers}; diff --git a/include/boost/capy/when_all.hpp b/include/boost/capy/when_all.hpp index 807c404be..94655d38e 100644 --- a/include/boost/capy/when_all.hpp +++ b/include/boost/capy/when_all.hpp @@ -331,9 +331,9 @@ make_when_all_homogeneous_runner(Awaitable inner, StateType* state, std::size_t { auto result = co_await std::move(inner); - if(result.ec) + if(std::get<0>(result)) { - state->record_error(result.ec); + state->record_error(std::get<0>(result)); state->core_.stop_source_.request_stop(); } else @@ -354,7 +354,7 @@ when_all_runner> make_when_all_io_runner(Awaitable inner, when_all_state* state) { auto result = co_await std::move(inner); - auto ec = result.ec; + auto ec = std::get<0>(result); std::get(state->results_).set(std::move(result)); if(ec) @@ -452,7 +452,11 @@ template auto extract_results(when_all_state& state) { return [&](std::index_sequence) { - return std::tuple(extract_single_result(state)...); + // Explicit element types: CTAD would collapse a single + // io_result child via the tuple copy deduction guide + return std::tuple< + decltype(extract_single_result(state))...>( + extract_single_result(state)...); }(std::index_sequence_for{}); } @@ -618,7 +622,7 @@ template for(auto& opt : state.results_) results.push_back(std::move(*opt)); - co_return io_result>{{}, std::move(results)}; + co_return io_result>{std::error_code(), std::move(results)}; } /** Execute a range of void io_result-returning awaitables concurrently. @@ -725,7 +729,7 @@ template auto r = detail::build_when_all_io_result( detail::extract_results(state)); if(state.has_error_.load(std::memory_order_relaxed)) - r.ec = state.first_error_; + std::get<0>(r) = state.first_error_; co_return r; } diff --git a/include/boost/capy/when_any.hpp b/include/boost/capy/when_any.hpp index 0db0e7f59..a9f91c03d 100644 --- a/include/boost/capy/when_any.hpp +++ b/include/boost/capy/when_any.hpp @@ -338,7 +338,7 @@ make_when_any_io_runner(Awaitable inner, StateType* state) { auto result = co_await std::move(inner); - if(!result.ec) + if(!std::get<0>(result)) { // Success: try to claim winner if(state->core_.try_win(I)) @@ -358,7 +358,7 @@ make_when_any_io_runner(Awaitable inner, StateType* state) else { // Error: record but don't win - state->record_error(result.ec); + state->record_error(std::get<0>(result)); } } @@ -515,7 +515,7 @@ make_when_any_io_homogeneous_runner( { auto result = co_await std::move(inner); - if(!result.ec) + if(!std::get<0>(result)) { if(state->core_.try_win(index)) { @@ -538,7 +538,7 @@ make_when_any_io_homogeneous_runner( } else { - state->record_error(result.ec); + state->record_error(std::get<0>(result)); } } diff --git a/include/boost/capy/write.hpp b/include/boost/capy/write.hpp index 354123ab4..90c39c124 100644 --- a/include/boost/capy/write.hpp +++ b/include/boost/capy/write.hpp @@ -109,7 +109,7 @@ auto write(S& stream, CB buffers) -> io_task co_return {ec, total_written}; } - co_return {{}, total_written}; + co_return {std::error_code(), total_written}; } } // namespace capy diff --git a/include/boost/capy/write_at_least.hpp b/include/boost/capy/write_at_least.hpp index 1a910a3b7..5674ad029 100644 --- a/include/boost/capy/write_at_least.hpp +++ b/include/boost/capy/write_at_least.hpp @@ -126,7 +126,7 @@ write_at_least(S& stream, CB buffers, std::size_t n) -> io_task co_return {ec, total_written}; } - co_return {{}, total_written}; + co_return {std::error_code(), total_written}; } } // namespace capy diff --git a/test/doc/programs/8m_parallel_tasks_variadic.cpp b/test/doc/programs/8m_parallel_tasks_variadic.cpp index 0d64b2da4..2380ac1c0 100644 --- a/test/doc/programs/8m_parallel_tasks_variadic.cpp +++ b/test/doc/programs/8m_parallel_tasks_variadic.cpp @@ -32,7 +32,7 @@ capy::io_task partial_sum(int lo, int hi) long long sum = 0; for (int i = lo; i < hi; ++i) sum += i; - co_return capy::io_result{{}, sum}; + co_return capy::io_result{std::error_code(), sum}; } int main() diff --git a/test/doc/snippets/4e_cancellation.cpp b/test/doc/snippets/4e_cancellation.cpp index 44540a937..bd4d03c13 100644 --- a/test/doc/snippets/4e_cancellation.cpp +++ b/test/doc/snippets/4e_cancellation.cpp @@ -268,7 +268,7 @@ capy::io_task await_fetch(fetch_channel& ch) ch.cancelled.store(true); co_return capy::io_result{ec, {}}; } - co_return capy::io_result{{}, std::move(ch.result)}; + co_return capy::io_result{std::error_code(), std::move(ch.result)}; } // The other side of the race: completes once whatever plays the diff --git a/test/doc/snippets/4f_composition.cpp b/test/doc/snippets/4f_composition.cpp index 5885ca9a1..62e504b15 100644 --- a/test/doc/snippets/4f_composition.cpp +++ b/test/doc/snippets/4f_composition.cpp @@ -103,9 +103,9 @@ namespace when_all_basics { // tag::when_all_basic[] -io_task fetch_a() { co_return io_result{{}, 1}; } -io_task fetch_b() { co_return io_result{{}, 2}; } -io_task fetch_c() { co_return io_result{{}, "hello"}; } +io_task fetch_a() { co_return io_result{std::error_code(), 1}; } +io_task fetch_b() { co_return io_result{std::error_code(), 2}; } +io_task fetch_c() { co_return io_result{std::error_code(), "hello"}; } task<> example() { @@ -124,7 +124,7 @@ namespace void_mix { // tag::when_all_void_mix[] io_task<> void_task() { co_return io_result<>{}; } -io_task int_task() { co_return io_result{{}, 42}; } +io_task int_task() { co_return io_result{std::error_code(), 42}; } task<> example() { @@ -146,7 +146,7 @@ io_task<> void_task_b() { co_return io_result<>{}; } task<> example() { auto r = co_await when_all(void_task_a(), void_task_b()); - if (r.ec) + if (std::get<0>(r)) { // handle error } @@ -157,7 +157,7 @@ task<> example() namespace error_handling { -io_task task_a() { co_return io_result{{}, 1}; } +io_task task_a() { co_return io_result{std::error_code(), 1}; } io_task task_b() { co_return io_result{error::timeout, 0}; } // tag::when_all_error[] @@ -178,7 +178,7 @@ io_task might_throw(bool fail) { if (fail) throw std::runtime_error("failed"); - co_return io_result{{}, 42}; + co_return io_result{std::error_code(), 42}; } task<> example() @@ -228,8 +228,8 @@ io_task<> long_running() namespace any_basic { -io_task fetch_int() { co_return io_result{{}, 7}; } -io_task fetch_string() { co_return io_result{{}, "s"}; } +io_task fetch_int() { co_return io_result{std::error_code(), 7}; } +io_task fetch_string() { co_return io_result{std::error_code(), "s"}; } // tag::when_any_basic[] @@ -274,7 +274,7 @@ io_task<> inner() { co_return io_result<>{error::timeout}; } io_task wrapped() { auto [ec] = co_await inner(); - co_return io_result{{}, ec}; + co_return io_result{std::error_code(), ec}; } // when_any(wrapped(), ...) -> variant @@ -295,17 +295,17 @@ struct page_data io_task fetch_header(std::string url) { - co_return io_result{{}, url + ":header"}; + co_return io_result{std::error_code(), url + ":header"}; } io_task fetch_body(std::string url) { - co_return io_result{{}, url + ":body"}; + co_return io_result{std::error_code(), url + ":body"}; } io_task fetch_sidebar(std::string url) { - co_return io_result{{}, url + ":sidebar"}; + co_return io_result{std::error_code(), url + ":sidebar"}; } // tag::parallel_fetch[] @@ -319,7 +319,7 @@ io_task fetch_page_data(std::string url) if (ec) co_return io_result{ec, {}}; - co_return io_result{{}, { + co_return io_result{std::error_code(), { std::move(header), std::move(body), std::move(sidebar) @@ -358,7 +358,7 @@ task process_all(std::vector const& items) io_task process_item(item const& i) { - co_return io_result{{}, i.value}; + co_return io_result{std::error_code(), i.value}; } } // namespace fanout @@ -426,7 +426,7 @@ struct composition_test auto r = co_await when_all( all_void::void_task_a(), all_void::void_task_b()); - BOOST_TEST(!r.ec); + BOOST_TEST(!std::get<0>(r)); checked = true; }; test::run_blocking()(check()); @@ -495,7 +495,7 @@ struct composition_test auto r = co_await when_all( stop_prop::fail_fast(), stop_prop::long_running()); - BOOST_TEST(r.ec == cond::timeout); + BOOST_TEST(std::get<0>(r) == cond::timeout); checked = true; }; test::run_blocking()(check()); diff --git a/test/unit/ex/immediate.cpp b/test/unit/ex/immediate.cpp index fec1826b9..da9488ee5 100644 --- a/test/unit/ex/immediate.cpp +++ b/test/unit/ex/immediate.cpp @@ -45,7 +45,7 @@ struct immediate_test // immediate> is always ready { - immediate> im{{{}, 100}}; + immediate> im{{std::error_code(), 100}}; BOOST_TEST(im.await_ready()); } } @@ -69,15 +69,15 @@ struct immediate_test { immediate> im{{}}; auto r = im.await_resume(); - BOOST_TEST(!r.ec); + BOOST_TEST(!std::get<0>(r)); } // immediate> returns result with value { - immediate> im{{{}, 42}}; + immediate> im{{std::error_code(), 42}}; auto r = im.await_resume(); - BOOST_TEST(!r.ec); - BOOST_TEST_EQ(std::get<0>(r.values), 42u); + BOOST_TEST(!std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r), 42u); } } @@ -97,18 +97,18 @@ struct immediate_test // co_await immediate> { auto coro = []() -> io_task { - co_return co_await immediate>{{{}, 100}}; + co_return co_await immediate>{{std::error_code(), 100}}; }; io_result result{}; test::run_blocking([&](io_result v) { result = v; })(coro()); - BOOST_TEST(!result.ec); - BOOST_TEST_EQ(std::get<0>(result.values), 100u); + BOOST_TEST(!std::get<0>(result)); + BOOST_TEST_EQ(std::get<1>(result), 100u); } // Structured binding with co_await { auto coro = []() -> task { - auto [ec, n] = co_await immediate>{{{}, 50}}; + auto [ec, n] = co_await immediate>{{std::error_code(), 50}}; if(ec) co_return 0; co_return n; @@ -127,7 +127,7 @@ struct immediate_test auto im = ready(); BOOST_TEST(im.await_ready()); auto r = im.await_resume(); - BOOST_TEST(!r.ec); + BOOST_TEST(!std::get<0>(r)); } // co_await ready() @@ -150,8 +150,8 @@ struct immediate_test auto im = ready(std::size_t{42}); BOOST_TEST(im.await_ready()); auto r = im.await_resume(); - BOOST_TEST(!r.ec); - BOOST_TEST_EQ(std::get<0>(r.values), 42u); + BOOST_TEST(!std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r), 42u); } // co_await ready(n) @@ -176,9 +176,9 @@ struct immediate_test auto im = ready(42, 3.14); BOOST_TEST(im.await_ready()); auto r = im.await_resume(); - BOOST_TEST(!r.ec); - BOOST_TEST_EQ(std::get<0>(r.values), 42); - BOOST_TEST_EQ(std::get<1>(r.values), 3.14); + BOOST_TEST(!std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r), 42); + BOOST_TEST_EQ(std::get<2>(r), 3.14); } // co_await ready(a, b) @@ -203,10 +203,10 @@ struct immediate_test auto im = ready(1, 2, 3); BOOST_TEST(im.await_ready()); auto r = im.await_resume(); - BOOST_TEST(!r.ec); - BOOST_TEST_EQ(std::get<0>(r.values), 1); - BOOST_TEST_EQ(std::get<1>(r.values), 2); - BOOST_TEST_EQ(std::get<2>(r.values), 3); + BOOST_TEST(!std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r), 1); + BOOST_TEST_EQ(std::get<2>(r), 2); + BOOST_TEST_EQ(std::get<3>(r), 3); } // co_await ready(a, b, c) @@ -232,7 +232,7 @@ struct immediate_test auto im = ready(ec); BOOST_TEST(im.await_ready()); auto r = im.await_resume(); - BOOST_TEST(r.ec); + BOOST_TEST(std::get<0>(r)); } // ready(ec, T1) creates failed single-value result @@ -241,8 +241,8 @@ struct immediate_test auto im = ready(ec, std::size_t{0}); BOOST_TEST(im.await_ready()); auto r = im.await_resume(); - BOOST_TEST(r.ec); - BOOST_TEST_EQ(std::get<0>(r.values), 0u); + BOOST_TEST(std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r), 0u); } // ready(ec, T1, T2) creates failed two-value result @@ -250,7 +250,7 @@ struct immediate_test auto ec = make_error_code(std::errc::invalid_argument); auto im = ready(ec, 0, 0.0); auto r = im.await_resume(); - BOOST_TEST(r.ec); + BOOST_TEST(std::get<0>(r)); } // ready(ec, T1, T2, T3) creates failed three-value result @@ -258,7 +258,7 @@ struct immediate_test auto ec = make_error_code(std::errc::invalid_argument); auto im = ready(ec, 0, 0, 0); auto r = im.await_resume(); - BOOST_TEST(r.ec); + BOOST_TEST(std::get<0>(r)); } // co_await with error diff --git a/test/unit/io/any_read_stream.cpp b/test/unit/io/any_read_stream.cpp index 7fe13cbcb..3e1b5675f 100644 --- a/test/unit/io/any_read_stream.cpp +++ b/test/unit/io/any_read_stream.cpp @@ -46,7 +46,7 @@ struct pending_read_awaitable std::coroutine_handle<> await_suspend(std::coroutine_handle<>, io_env const*) { return std::noop_coroutine(); } io_result await_resume() - { return {{}, 0}; } + { return {std::error_code(), 0}; } }; struct pending_read_stream @@ -66,7 +66,7 @@ struct resuming_read_awaitable std::coroutine_handle<> await_suspend(std::coroutine_handle<> h, io_env const*) noexcept { return h; } - io_result await_resume() { return {{}, 7}; } + io_result await_resume() { return {std::error_code(), 7}; } }; struct resuming_read_stream diff --git a/test/unit/io/any_stream.cpp b/test/unit/io/any_stream.cpp index 56b84e1a7..ab788a30c 100644 --- a/test/unit/io/any_stream.cpp +++ b/test/unit/io/any_stream.cpp @@ -90,7 +90,7 @@ class mock_stream self_->read_data_.data() + self_->read_pos_, avail); std::size_t const n = buffer_copy(buffers_, src); self_->read_pos_ += n; - return {{}, n}; + return {std::error_code(), n}; } }; return awaitable{this, buffers}; @@ -122,13 +122,13 @@ class mock_stream std::size_t n = buffer_size(buffers_); if(n == 0) - return {{}, 0}; + return {std::error_code(), 0}; std::size_t const old_size = self_->write_data_.size(); self_->write_data_.resize(old_size + n); buffer_copy(make_buffer( self_->write_data_.data() + old_size, n), buffers_, n); - return {{}, n}; + return {std::error_code(), n}; } }; return awaitable{this, buffers}; diff --git a/test/unit/io/any_write_stream.cpp b/test/unit/io/any_write_stream.cpp index c75aaa278..282b19796 100644 --- a/test/unit/io/any_write_stream.cpp +++ b/test/unit/io/any_write_stream.cpp @@ -46,7 +46,7 @@ struct pending_write_awaitable std::coroutine_handle<> await_suspend(std::coroutine_handle<>, io_env const*) { return std::noop_coroutine(); } io_result await_resume() - { return {{}, 0}; } + { return {std::error_code(), 0}; } }; struct pending_write_stream diff --git a/test/unit/io/write_now.cpp b/test/unit/io/write_now.cpp index bd416803b..f9ba3db05 100644 --- a/test/unit/io/write_now.cpp +++ b/test/unit/io/write_now.cpp @@ -37,7 +37,7 @@ struct suspending_write_awaitable std::coroutine_handle<> await_suspend(std::coroutine_handle<> h, io_env const*) noexcept { return h; } - io_result await_resume() { return {{}, 5}; } + io_result await_resume() { return {std::error_code(), 5}; } }; struct suspending_write_stream diff --git a/test/unit/io_result.cpp b/test/unit/io_result.cpp index 9c01edc07..50b627325 100644 --- a/test/unit/io_result.cpp +++ b/test/unit/io_result.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "test_suite.hpp" @@ -24,11 +25,11 @@ struct io_result_test { // Default construction io_result<> r1; - BOOST_TEST(!r1.ec); + BOOST_TEST(!std::get<0>(r1)); // With error io_result<> r2{make_error_code(std::errc::invalid_argument)}; - BOOST_TEST(r2.ec); + BOOST_TEST(std::get<0>(r2)); // Structured binding auto [ec] = r1; @@ -40,19 +41,19 @@ struct io_result_test { // Default construction io_result r1; - BOOST_TEST(!r1.ec); - BOOST_TEST_EQ(std::get<0>(r1.values), 0u); + BOOST_TEST(!std::get<0>(r1)); + BOOST_TEST_EQ(std::get<1>(r1), 0u); // With values - io_result r2{{}, 42}; - BOOST_TEST(!r2.ec); - BOOST_TEST_EQ(std::get<0>(r2.values), 42u); + io_result r2{std::error_code(), 42}; + BOOST_TEST(!std::get<0>(r2)); + BOOST_TEST_EQ(std::get<1>(r2), 42u); // With error io_result r3{ make_error_code(std::errc::invalid_argument), 10}; - BOOST_TEST(r3.ec); - BOOST_TEST_EQ(std::get<0>(r3.values), 10u); + BOOST_TEST(std::get<0>(r3)); + BOOST_TEST_EQ(std::get<1>(r3), 10u); // Structured binding auto [ec, n] = r2; @@ -64,9 +65,9 @@ struct io_result_test testGenericSingleValue() { // With string value - io_result r1{{}, "hello"}; - BOOST_TEST(!r1.ec); - BOOST_TEST_EQ(std::get<0>(r1.values), "hello"); + io_result r1{std::error_code(), "hello"}; + BOOST_TEST(!std::get<0>(r1)); + BOOST_TEST_EQ(std::get<1>(r1), "hello"); // Structured binding auto [ec, v] = r1; @@ -76,8 +77,8 @@ struct io_result_test // With error io_result r2{ make_error_code(std::errc::invalid_argument), "error"}; - BOOST_TEST(r2.ec); - BOOST_TEST_EQ(std::get<0>(r2.values), "error"); + BOOST_TEST(std::get<0>(r2)); + BOOST_TEST_EQ(std::get<1>(r2), "error"); } void @@ -86,10 +87,10 @@ struct io_result_test // With multiple values io_result r1{ {}, 42, 3.14, std::string("test")}; - BOOST_TEST(!r1.ec); - BOOST_TEST_EQ(std::get<0>(r1.values), 42); - BOOST_TEST_EQ(std::get<1>(r1.values), 3.14); - BOOST_TEST_EQ(std::get<2>(r1.values), "test"); + BOOST_TEST(!std::get<0>(r1)); + BOOST_TEST_EQ(std::get<1>(r1), 42); + BOOST_TEST_EQ(std::get<2>(r1), 3.14); + BOOST_TEST_EQ(std::get<3>(r1), "test"); // Structured binding auto [ec, a, b, c] = r1; @@ -101,9 +102,9 @@ struct io_result_test // With error io_result r2{ make_error_code(std::errc::invalid_argument), 0, 0.0}; - BOOST_TEST(r2.ec); - BOOST_TEST_EQ(std::get<0>(r2.values), 0); - BOOST_TEST_EQ(std::get<1>(r2.values), 0.0); + BOOST_TEST(std::get<0>(r2)); + BOOST_TEST_EQ(std::get<1>(r2), 0); + BOOST_TEST_EQ(std::get<2>(r2), 0.0); } void @@ -112,11 +113,11 @@ struct io_result_test // Verify no arity limit io_result r1{ {}, 1, 2.5, std::string("hi"), true}; - BOOST_TEST(!r1.ec); - BOOST_TEST_EQ(std::get<0>(r1.values), 1); - BOOST_TEST_EQ(std::get<1>(r1.values), 2.5); - BOOST_TEST_EQ(std::get<2>(r1.values), "hi"); - BOOST_TEST_EQ(std::get<3>(r1.values), true); + BOOST_TEST(!std::get<0>(r1)); + BOOST_TEST_EQ(std::get<1>(r1), 1); + BOOST_TEST_EQ(std::get<2>(r1), 2.5); + BOOST_TEST_EQ(std::get<3>(r1), "hi"); + BOOST_TEST_EQ(std::get<4>(r1), true); // Structured binding auto [ec, a, b, c, d] = r1; @@ -128,9 +129,80 @@ struct io_result_test // Default construction io_result r2; - BOOST_TEST(!r2.ec); - BOOST_TEST_EQ(std::get<0>(r2.values), 0); - BOOST_TEST_EQ(std::get<3>(r2.values), false); + BOOST_TEST(!std::get<0>(r2)); + BOOST_TEST_EQ(std::get<1>(r2), 0); + BOOST_TEST_EQ(std::get<4>(r2), false); + } + + void + testTie() + { + // std::tie rebinds into existing variables, and + // avoids structured bindings entirely + std::error_code ec; + std::size_t n = 0; + std::tie(ec, n) = io_result{std::error_code(), 42}; + BOOST_TEST(!ec); + BOOST_TEST_EQ(n, 42u); + + std::tie(ec, n) = io_result{ + make_error_code(std::errc::invalid_argument), 7}; + BOOST_TEST(ec); + BOOST_TEST_EQ(n, 7u); + + // Partial rebinding with std::ignore + std::tie(ec, std::ignore) = + io_result{std::error_code(), 99}; + BOOST_TEST(!ec); + BOOST_TEST_EQ(n, 7u); + } + + void + testApply() + { + io_result r{std::error_code(), 42}; + auto sum = std::apply( + [](std::error_code ec, std::size_t n) + { + return ec ? 0u : n; + }, r); + BOOST_TEST_EQ(sum, 42u); + } + + void + testTupleInterop() + { + // Comparison + io_result r1{std::error_code(), 42}; + io_result r2{std::error_code(), 42}; + BOOST_TEST(r1 == r2); + + // Assignment from a plain tuple + r1 = std::tuple{ + make_error_code(std::errc::invalid_argument), 10}; + BOOST_TEST(r1 != r2); + BOOST_TEST_EQ(std::get<1>(r1), 10u); + + // tuple_cat + auto joined = std::tuple_cat( + r2, std::tuple{7}); + BOOST_TEST_EQ(std::get<2>(joined), 7); + } + + void + testTraits() + { + // Outcome detection is structural: any tuple with a + // leading error_code qualifies, no others do + static_assert(detail::is_io_result_v>); + static_assert(detail::is_io_result_v< + io_result>); + static_assert(detail::is_io_result_v< + std::tuple>); + static_assert(!detail::is_io_result_v< + std::tuple>); + static_assert(!detail::is_io_result_v>); + static_assert(!detail::is_io_result_v); } void @@ -141,6 +213,10 @@ struct io_result_test testGenericSingleValue(); testMultiValue(); testFourPlusArgs(); + testTie(); + testApply(); + testTupleInterop(); + testTraits(); } }; diff --git a/test/unit/quitter.cpp b/test/unit/quitter.cpp index 0ab30446b..d15a67fe9 100644 --- a/test/unit/quitter.cpp +++ b/test/unit/quitter.cpp @@ -643,7 +643,7 @@ struct quitter_test static quitter> quitter_success_size(std::size_t n) { - co_return io_result{{}, n}; + co_return io_result{std::error_code(), n}; } void diff --git a/test/unit/when_all.cpp b/test/unit/when_all.cpp index e438b5fce..79d6d5bb2 100644 --- a/test/unit/when_all.cpp +++ b/test/unit/when_all.cpp @@ -89,17 +89,17 @@ struct when_all_strand_test auto outer = [&]() -> task> { co_return co_await when_all( []() -> io_task { - co_return io_result{{}, 10}; + co_return io_result{std::error_code(), 10}; }(), []() -> io_task { - co_return io_result{{}, 20}; + co_return io_result{std::error_code(), 20}; }()); }; run_async(s, [&](io_result r) { completed = true; - result = std::get<0>(r.values) + std::get<1>(r.values); + result = std::get<1>(r) + std::get<2>(r); done.count_down(); }, [&](auto) { @@ -134,7 +134,7 @@ namespace { io_task io_success_size(size_t n) { - co_return io_result{{}, n}; + co_return io_result{std::error_code(), n}; } io_task @@ -171,7 +171,7 @@ io_task io_throws_size(char const* msg) { throw test_exception(msg); - co_return io_result{{}, 0}; + co_return io_result{std::error_code(), 0}; } #if defined(_MSC_VER) @@ -181,13 +181,13 @@ io_throws_size(char const* msg) io_task io_success_string(std::string s) { - co_return io_result{{}, std::move(s)}; + co_return io_result{std::error_code(), std::move(s)}; } io_task io_success_size_int(size_t n, int flags) { - co_return io_result{{}, n, flags}; + co_return io_result{std::error_code(), n, flags}; } // Suspends until stop token fires, then returns ECANCELED. @@ -215,9 +215,9 @@ struct when_all_range_test run_async(ex, [&](io_result> r) { completed = true; - BOOST_TEST(!r.ec); - BOOST_TEST_EQ(std::get<0>(r.values).size(), 1u); - BOOST_TEST_EQ(std::get<0>(r.values)[0], 42u); + BOOST_TEST(!std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r).size(), 1u); + BOOST_TEST_EQ(std::get<1>(r)[0], 42u); }, [](std::exception_ptr) {})( when_all(std::move(tasks))); @@ -240,11 +240,11 @@ struct when_all_range_test run_async(ex, [&](io_result> r) { completed = true; - BOOST_TEST(!r.ec); - BOOST_TEST_EQ(std::get<0>(r.values).size(), 3u); - BOOST_TEST_EQ(std::get<0>(r.values)[0], 10u); - BOOST_TEST_EQ(std::get<0>(r.values)[1], 20u); - BOOST_TEST_EQ(std::get<0>(r.values)[2], 30u); + BOOST_TEST(!std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r).size(), 3u); + BOOST_TEST_EQ(std::get<1>(r)[0], 10u); + BOOST_TEST_EQ(std::get<1>(r)[1], 20u); + BOOST_TEST_EQ(std::get<1>(r)[2], 30u); }, [](std::exception_ptr) {})( when_all(std::move(tasks))); @@ -289,7 +289,7 @@ struct when_all_range_test run_async(ex, [&](io_result<> r) { completed = true; - BOOST_TEST(!r.ec); + BOOST_TEST(!std::get<0>(r)); }, [](std::exception_ptr) {})( when_all(std::move(tasks))); @@ -334,7 +334,7 @@ struct when_all_range_test run_async(ex, [&](io_result> r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all(std::move(tasks))); @@ -358,7 +358,7 @@ struct when_all_range_test run_async(ex, [&](io_result> r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all(std::move(tasks))); @@ -410,7 +410,7 @@ struct when_all_range_test run_async(ex, [&](io_result<> r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all(std::move(tasks))); @@ -477,7 +477,7 @@ struct when_all_range_test auto counting_io = [&]() -> io_task { ++completion_count; - co_return io_result{{}, 1}; + co_return io_result{std::error_code(), 1}; }; auto failing_io = [&]() -> io_task { @@ -515,7 +515,7 @@ struct when_all_range_test run_async(ex, [&](io_result> r) { success_called = true; - BOOST_TEST(!!r.ec); + BOOST_TEST(!!std::get<0>(r)); }, [&](std::exception_ptr) { error_called = true; @@ -540,10 +540,10 @@ struct when_all_range_test run_async(ex, [&](io_result> r) { completed = true; - BOOST_TEST(!r.ec); - BOOST_TEST_EQ(std::get<0>(r.values)[0], "first"); - BOOST_TEST_EQ(std::get<0>(r.values)[1], "second"); - BOOST_TEST_EQ(std::get<0>(r.values)[2], "third"); + BOOST_TEST(!std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r)[0], "first"); + BOOST_TEST_EQ(std::get<1>(r)[1], "second"); + BOOST_TEST_EQ(std::get<1>(r)[2], "third"); }, [](std::exception_ptr) {})( when_all(std::move(tasks))); @@ -567,16 +567,16 @@ struct when_all_range_test }; auto io_size_task = []() -> io_task { - co_return io_result{{}, 99}; + co_return io_result{std::error_code(), 99}; }; run_async(ex, [&](io_result, size_t> r) { completed = true; - BOOST_TEST(!r.ec); - BOOST_TEST_EQ(std::get<0>(r.values).size(), 3u); - BOOST_TEST_EQ(std::get<0>(r.values)[0] + std::get<0>(r.values)[1] + std::get<0>(r.values)[2], 6u); - BOOST_TEST_EQ(std::get<1>(r.values), 99u); + BOOST_TEST(!std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r).size(), 3u); + BOOST_TEST_EQ(std::get<1>(r)[0] + std::get<1>(r)[1] + std::get<1>(r)[2], 6u); + BOOST_TEST_EQ(std::get<2>(r), 99u); }, [](std::exception_ptr) {})( when_all(range_task(), io_size_task())); @@ -603,8 +603,8 @@ struct when_all_range_test run_async(s, [&](io_result> r) { completed = true; - BOOST_TEST(!r.ec); - result = std::get<0>(r.values)[0] + std::get<0>(r.values)[1]; + BOOST_TEST(!std::get<0>(r)); + result = std::get<1>(r)[0] + std::get<1>(r)[1]; done.count_down(); }, [&](auto) { @@ -707,10 +707,10 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - BOOST_TEST(!r.ec); - n1 = std::get<0>(r.values); - n2 = std::get<1>(r.values); - n3 = std::get<2>(r.values); + BOOST_TEST(!std::get<0>(r)); + n1 = std::get<1>(r); + n2 = std::get<2>(r); + n3 = std::get<3>(r); }, [](std::exception_ptr) {})( when_all( @@ -736,8 +736,8 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - BOOST_TEST(!r.ec); - result = std::get<0>(r.values); + BOOST_TEST(!std::get<0>(r)); + result = std::get<1>(r); }, [](std::exception_ptr) {})( when_all(io_success_size(42))); @@ -759,7 +759,7 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all( @@ -783,7 +783,7 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all( @@ -807,7 +807,7 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all( @@ -832,8 +832,8 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - result_ec = r.ec; - partial = std::get<0>(r.values); + result_ec = std::get<0>(r); + partial = std::get<1>(r); }, [](std::exception_ptr) {})( when_all( @@ -857,8 +857,8 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - result_ec = r.ec; - n1 = std::get<0>(r.values); + result_ec = std::get<0>(r); + n1 = std::get<1>(r); }, [](std::exception_ptr) {})( when_all( @@ -882,8 +882,8 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - BOOST_TEST(!r.ec); - BOOST_TEST_EQ(std::get<0>(r.values), 0u); + BOOST_TEST(!std::get<0>(r)); + BOOST_TEST_EQ(std::get<1>(r), 0u); }, [](std::exception_ptr) {})( when_all(io_success_size(0))); @@ -904,7 +904,7 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all( @@ -1035,7 +1035,7 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all( @@ -1059,7 +1059,7 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all( @@ -1084,7 +1084,7 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { success_called = true; - BOOST_TEST(!!r.ec); + BOOST_TEST(!!std::get<0>(r)); }, [&](std::exception_ptr) { error_called = true; @@ -1108,9 +1108,9 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - BOOST_TEST(!r.ec); - n = std::get<0>(r.values); - s = std::get<1>(r.values); + BOOST_TEST(!std::get<0>(r)); + n = std::get<1>(r); + s = std::get<2>(r); }, [](std::exception_ptr) {})( when_all( @@ -1135,9 +1135,9 @@ struct when_all_io_result_test run_async(ex, [&](io_result> r) { completed = true; - BOOST_TEST(!r.ec); - n = std::get<0>(r.values); - tf = std::get<1>(r.values); + BOOST_TEST(!std::get<0>(r)); + n = std::get<1>(r); + tf = std::get<2>(r); }, [](std::exception_ptr) {})( when_all( @@ -1162,8 +1162,8 @@ struct when_all_io_result_test run_async(ex, [&](io_result> r) { completed = true; - BOOST_TEST(!r.ec); - n = std::get<0>(r.values); + BOOST_TEST(!std::get<0>(r)); + n = std::get<1>(r); }, [](std::exception_ptr) {})( when_all( @@ -1188,7 +1188,7 @@ struct when_all_io_result_test run_async(ex, [&](io_result r) { completed = true; - result_ec = r.ec; + result_ec = std::get<0>(r); }, [](std::exception_ptr) {})( when_all( diff --git a/test/unit/when_any.cpp b/test/unit/when_any.cpp index 5a28ec91d..55940177f 100644 --- a/test/unit/when_any.cpp +++ b/test/unit/when_any.cpp @@ -37,7 +37,7 @@ namespace { io_task io_success_size(size_t n) { - co_return io_result{{}, n}; + co_return io_result{std::error_code(), n}; } io_task @@ -49,7 +49,7 @@ io_error_size(std::error_code ec, size_t n = 0) io_task io_success_string(std::string s) { - co_return io_result{{}, std::move(s)}; + co_return io_result{std::error_code(), std::move(s)}; } #if defined(_MSC_VER) @@ -61,7 +61,7 @@ io_task io_throws_size(char const* msg) { throw test_exception(msg); - co_return io_result{{}, 0}; + co_return io_result{std::error_code(), 0}; } #if defined(_MSC_VER) @@ -97,7 +97,7 @@ struct immediate_io_awaitable io_result await_resume() { - return io_result{{}, n_}; + return io_result{std::error_code(), n_}; } }; @@ -132,7 +132,8 @@ struct throwing_payload_awaitable } io_result await_resume() { - return io_result{{}, throwing_move_payload{7}}; + return io_result{ + std::error_code(), throwing_move_payload{7}}; } }; @@ -454,7 +455,7 @@ struct when_any_vector_test auto counting = [&](size_t value) -> io_task { ++completion_count; - co_return io_result{{}, value}; + co_return io_result{std::error_code(), value}; }; std::vector> tasks; @@ -486,7 +487,7 @@ struct when_any_vector_test auto fast = [&]() -> io_task { ++completed_normally_count; - co_return io_result{{}, 42}; + co_return io_result{std::error_code(), 42}; }; auto slow = [&](size_t id, int steps) -> io_task { @@ -499,7 +500,7 @@ struct when_any_vector_test co_await yield_awaitable{}; } ++completed_normally_count; - co_return io_result{{}, id}; + co_return io_result{std::error_code(), id}; }; std::vector> tasks; @@ -568,7 +569,7 @@ struct when_any_vector_test tasks.push_back(io_success_size(20)); auto v = co_await when_any(std::move(tasks)); if(v.index() == 1) - co_return io_result{{}, std::get<1>(v).second}; + co_return io_result{std::error_code(), std::get<1>(v).second}; co_return io_result{std::get<0>(v), 0}; };