-
Notifications
You must be signed in to change notification settings - Fork 11
Feature: when_any #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Feature: when_any #101
Conversation
📝 WalkthroughWalkthroughAdds a new when_any facility (heterogeneous and vector forms) with public APIs, umbrella header exposure, documentation and nav updates, extensive unit tests, a safety comment in when_all, and consolidated test helpers. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Launcher as when_any_launcher
participant State as when_any_state
participant Runner0 as when_any_runner[0]
participant Runner1 as when_any_runner[1]
participant Task0 as task[0]
participant Task1 as task[1]
Client->>Launcher: call when_any(task0, task1)
Launcher->>State: create shared state (remaining=N)
Launcher->>Runner0: spawn runner for task0
Launcher->>Runner1: spawn runner for task1
par concurrent
Runner0->>Task0: co_await inner
Runner1->>Task1: co_await inner
end
alt Task0 completes first
Task0->>Runner0: complete (result/exception)
Runner0->>State: try_win(index=0)
State->>State: set winner, store result/exception
Runner0->>State: signal_completion()
else Task1 completes first
Task1->>Runner1: complete (result/exception)
Runner1->>State: try_win(index=1)
State->>State: set winner, store result/exception
Runner1->>State: signal_completion()
end
Note over State: Non-winning tasks may be cancelled or ignored
State->>Launcher: all runners completed
Launcher->>Client: return (winner_index, winner_result) or rethrow winner exception
Estimated Code Review Effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@include/boost/capy/when_any.hpp`:
- Line 7: Update the header comment repository URL in
include/boost/capy/when_any.hpp to point to the correct "capy" repository
(replace the incorrect https://github.com/cppalliance/corosio link with the
project's actual capy repo URL used elsewhere in this project), by editing the
top-of-file comment so it references the canonical capy repository URL.
- Around line 447-449: When a coroutine wins (state->try_win(Index)) the
subsequent call to state->set_winner_result(std::move(result)) may throw and its
exception would be lost; update both winner paths (the block using auto result =
co_await std::move(inner); and the second location) to wrap
set_winner_result(...) in a try/catch, and on catch call
state->set_winner_exception(std::current_exception()) so exceptions are recorded
instead of being dropped; locate the calls to try_win, set_winner_result and add
the try/catch that invokes set_winner_exception on error.
- Around line 153-156: The instantiation of std::variant<> inside
unique_variant_t is ill-formed; change the accumulator to start from the first
pack type so the variant is always well-formed: modify unique_variant_t to
initialize deduplicate_impl with std::variant<void_to_monostate_t<T0>> (i.e.,
use the first pack element) and then pass the remaining
void_to_monostate_t<Ts>... to deduplicate_impl so the accumulator variant is
constructed from a real type; reference unique_variant_t, deduplicate_impl and
void_to_monostate_t when making this change.
In `@test/unit/when_any.cpp`:
- Line 7: Replace the incorrect repository URL comment in test/unit/when_any.cpp
that references "https://github.com/cppalliance/corosio" with the correct capy
repository URL used by this project; locate the top-of-file comment (the
repository URL string) and update it to the project's canonical URL (or remove
the stray copy-paste comment) so the file no longer points to corosio.
|
An automated preview of the documentation is available at https://101.capy.prtest3.cppalliance.org/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-01-23 22:28:08 UTC |
|
GCOVR code coverage report https://101.capy.prtest3.cppalliance.org/gcovr/index.html Build time: 2026-01-23 22:34:29 UTC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@include/boost/capy/when_any.hpp`:
- Around line 1288-1293: The homogenous branch after co_await inner must mirror
the heterogeneous runner's exception handling: after calling
state->try_win(index) and before/around
state->set_winner_result(std::move(result)), wrap the set_winner_result call in
a try/catch; on catch(...) capture the exception via std::current_exception()
and forward it to the same error path used elsewhere (e.g., call
state->set_winner_exception(...) or the equivalent error-setter used by the
heterogeneous make_when_any_runner), ensuring you still only call the setter
when try_win(index) succeeded and that exceptions from set_winner_result are
propagated into the state's exception storage.
🧹 Nitpick comments (2)
include/boost/capy/when_any.hpp (1)
658-661: Consider edge case:await_readymay never return true.The condition
sizeof...(Ts) == 0will never be true at runtime sincewhen_anyrequires at least one task (T0in the template). This makesawait_readyeffectively always returnfalse, which is fine but the comment "degenerate case" is somewhat misleading. The variadicwhen_anyenforces at least one argument at the API level.test/unit/when_any.cpp (1)
596-600: Minor: Consider usingstd::dequefor more efficient front removal.The work queue processing pattern uses
erase(begin())on a vector, which is O(n) for each removal. For large queue sizes, consider usingstd::dequeinstead for O(1) front removal.♻️ Optional improvement
- std::vector<coro> work_queue; + std::deque<coro> work_queue; queuing_executor ex(work_queue); ... while (!work_queue.empty()) { auto h = work_queue.front(); - work_queue.erase(work_queue.begin()); + work_queue.pop_front(); h.resume(); }This would require updating
queuing_executorto usestd::deque<coro>*instead ofstd::vector<coro>*.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@include/boost/capy/when_any.hpp`:
- Around line 248-363: The member variant_type result_ is default-initialized
which forces the first alternative T0 to be default-constructible; change
storage to be lazy by replacing result_ with std::optional<variant_type> (e.g.
result_opt_), initialize it empty, and emplace the variant only when the winner
stores a value: update set_winner_result<T> to emplace
result_opt_.emplace().template emplace<T>(...) and set_winner_void() to
result_opt_.emplace().template emplace<std::monostate>(...), and update any
accessors/readers to use *result_opt_ (or check for engagement) so
when_any_state and try_win logic no longer requires T0 to be
default-constructible.
|
@coderabbitai generate docstrings |
✅ Actions performedInitiated docstring generation; will generate only if new commits exist. |
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @mvandeberg. * #101 (comment) The following files were modified: * `include/boost/capy/when_all.hpp` * `include/boost/capy/when_any.hpp` * `test/unit/when_any.cpp`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@include/boost/capy/when_any.hpp`:
- Around line 965-969: The homogeneous set_winner_result currently assigns into
result_ (result_ = std::move(value)) which imposes MoveAssignable; change it to
construct in-place using result_.emplace(std::move(value)) and relax the
noexcept to match construction (e.g.
noexcept(std::is_nothrow_move_constructible_v<T>)) so
move-constructible-but-non-assignable T is accepted just like the heterogeneous
path.
- Around line 822-825: Introduce an atomic flag winner_ready_ in the shared
state and use it to establish a release-acquire synchronization: when you
nominate a winner (the code path that writes state.result_,
state.winner_exception_, and state.winner_index_ and then calls dispatch()),
store winner_ready_.store(true, std::memory_order_release) immediately after
those non-atomic writes; on the when_any read side (inside when_any before
reading state.result_ or state.winner_exception_), perform
if(!winner_ready_.load(std::memory_order_acquire)) then wait/loop or otherwise
ensure acquisition before accessing those fields so the reads see the published
winner data even if a non-winner performed the final
remaining_count_.fetch_sub(acq_rel); also update the existing memory-model
comment (around the previous lines 82–87) to document this release on winner
write and acquire on winner read and why it is needed for correct
synchronization with remaining_count_.fetch_sub(acq_rel), referencing
remaining_count_.fetch_sub(acq_rel), dispatch(), when_any, winner_ready_,
state.result_, state.winner_exception_, and winner_index_.
| if(state.winner_exception_) | ||
| std::rethrow_exception(state.winner_exception_); | ||
|
|
||
| co_return result_type{state.winner_index_, std::move(*state.result_)}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the file
find . -name "when_any.hpp" -type fRepository: cppalliance/capy
Length of output: 93
🏁 Script executed:
# Get file size and examine the relevant section
wc -l include/boost/capy/when_any.hppRepository: cppalliance/capy
Length of output: 96
🏁 Script executed:
# Read the context around lines 822-825
sed -n '810,835p' include/boost/capy/when_any.hppRepository: cppalliance/capy
Length of output: 930
🏁 Script executed:
# Find the when_any_state definition and try_win() method
sed -n '1,100p' include/boost/capy/when_any.hpp | head -100Repository: cppalliance/capy
Length of output: 3548
🏁 Script executed:
# Search for when_any_state struct definition
grep -n "struct when_any_state" include/boost/capy/when_any.hppRepository: cppalliance/capy
Length of output: 85
🏁 Script executed:
# Search for try_win method and remaining_count_
rg -n "try_win|remaining_count_" include/boost/capy/when_any.hppRepository: cppalliance/capy
Length of output: 1646
🏁 Script executed:
# Read the when_any_state definition around line 223
sed -n '223,380p' include/boost/capy/when_any.hppRepository: cppalliance/capy
Length of output: 5248
🏁 Script executed:
# Read the try_win implementation
sed -n '329,350p' include/boost/capy/when_any.hppRepository: cppalliance/capy
Length of output: 784
🏁 Script executed:
# Read the signal_completion implementation
sed -n '378,395p' include/boost/capy/when_any.hppRepository: cppalliance/capy
Length of output: 676
🏁 Script executed:
# Look for the launcher/co_await logic to see how parent waits
rg -n "when_any_launcher" include/boost/capy/when_any.hpp -A 5 | head -40Repository: cppalliance/capy
Length of output: 1255
🏁 Script executed:
# Read lines 82-87 for the full memory model comment
sed -n '40,100p' include/boost/capy/when_any.hppRepository: cppalliance/capy
Length of output: 2502
🏁 Script executed:
# Let me look at how co_await awaits and what happens at that point
sed -n '680,750p' include/boost/capy/when_any.hppRepository: cppalliance/capy
Length of output: 2540
🏁 Script executed:
# Check the await_suspend implementation
rg -n "await_suspend|await_ready|await_resume" include/boost/capy/when_any.hpp -B 2 -A 8Repository: cppalliance/capy
Length of output: 8959
🏁 Script executed:
# Look for executor dispatch to understand synchronization
rg -n "dispatch" include/boost/capy/when_any.hpp -B 2 -A 4Repository: cppalliance/capy
Length of output: 3969
🌐 Web query:
C++ std::memory_order_acq_rel fetch_sub release sequence synchronization multiple threads atomics
💡 Result:
Short answer: A read-modify-write like atomic.fetch_sub(..., std::memory_order_acq_rel) is both a release and an acquire (RMW with acq_rel). As an RMW it can be part of a release-sequence (release-store head + zero or more intervening RMWs of any memory_order). An acquire that observes a value produced anywhere in that release-sequence (including a later fetch_sub) synchronizes-with the release head, so the usual release→acquire happens-before guarantees propagate through those RMWs. In other words, fetch_sub(acq_rel) can both publish prior writes (release side) and establish visibility of earlier releases when observed (acquire side), and it participates in release-sequence synchronization. [1][2][3]
Sources:
- cppreference: memory_order and description of acq_rel and release-acquire semantics. [1]
- Explanations of “release sequence” and RMWs (fetch_*/CAS) being included in the release sequence even if relaxed, and how an acquire that reads a value from the sequence synchronizes-with the release head. [2][3]
🏁 Script executed:
# Check executor_ref dispatch implementation
fd "executor_ref" -type fRepository: cppalliance/capy
Length of output: 230
🏁 Script executed:
# Look at how dispatch is implemented
rg -n "dispatch" include/boost/capy/ex/ -A 5 | head -50Repository: cppalliance/capy
Length of output: 3142
🏁 Script executed:
# Check if there are any documented synchronization guarantees
rg -n "memory" include/boost/capy/ex/ -B 2 -A 2Repository: cppalliance/capy
Length of output: 2525
🌐 Web query:
C++ coroutine co_await resume synchronization memory ordering happens-before
💡 Result:
Short answer
- Suspending a coroutine at co_await fully stops its execution and the awaiter object is considered no longer used by the suspending thread; that lets await_suspend() publish the coroutine_handle to another thread without holding a lock. [1][2]
- That publication itself does not magically create C++ memory-order happens‑before edges for non-atomic writes; you must publish the handle using proper synchronization (e.g. store it into an atomic with release semantics or behind a mutex) and the thread that receives it must acquire/lock before calling resume(). Doing that establishes the required happens‑before so the resumed coroutine sees prior writes. cppreference explicitly recommends at least release on the publisher and acquire on the resumer. [1]
- Resuming the coroutine (handle.resume()) executes the coroutine body on the resumer thread; concurrent resume from multiple threads is UB (data‑race). [1][3]
Why (brief)
- The language guarantees the coroutine is "fully suspended" at the suspension point, which means the suspending thread can safely hand the handle to another thread as an operation; but that is a logical/thread-model guarantee, not a substitute for memory ordering between threads. To make written state visible across threads you still need the usual C++ synchronization idioms (atomics or mutexes) so that the publisher’s writes happen‑before the resume. [1][2]
Practical recipe
- In await_suspend(): prepare state, then publish the coroutine_handle using an atomic store(release) or push it onto a thread‑safe queue protected by a mutex.
- In the worker/completion thread: load the handle with acquire (or lock), then call handle.resume().
- Never let two threads call resume() on the same suspended handle concurrently. [1][2][3]
Sources
- cppreference: Coroutines (notes on await_suspend, publishing handle, and memory-safety guidance). [1]
- Lewis Baker: “Understanding operator co_await” (explanation of suspension and synchronization‑free patterns). [2]
- Related discussion (safety/UB concerns when resuming from multiple threads). [3]
🌐 Web query:
C++ std::memory_order_acq_rel release sequence non-atomic data synchronization memory visibility
💡 Result:
Short answer
- A read-modify-write (RMW) operation with memory_order_acq_rel acts both as a release (its store side) and an acquire (its load side). That makes it usable to publish prior non-atomic writes (release side) and to see prior writes from another thread (acquire side). [1]
Release sequence and visibility (key points)
- A "release sequence" is a maximal contiguous subsequence in the modification order of an atomic object whose head is a release operation; subsequent members must be either performed by the same thread as the head or be atomic RMW operations. An acquire load that reads a value from any member of that release sequence "synchronizes‑with" the head release. [2][3]
- When synchronization is established (release → acquire via the same atomic or via the release sequence), all writes (including non-atomic writes) that were sequenced‑before the release become visible to the acquiring thread. In other words: a release publish + an acquire read that observes that published value (or a value from the release sequence) gives the acquiring thread a guaranteed view of the publisher's prior non‑atomic stores. [1][2]
- Important subtlety: only RMW operations can extend a release sequence — ordinary relaxed or non‑RMW stores do not — so a later non‑RMW store in the modification order does not by itself give the acquire synchronization back to an earlier release. On many real CPUs an acquire will appear to sync with earlier releases, but the C++ memory model requires the release‑sequence rule for portability. [3][1]
Concise example
- Thread A:
write non‑atomic data D;
x.store(1, memory_order_release); - Thread B:
if (x.fetch_add(0, memory_order_acq_rel) /* or load(memory_order_acquire) that sees 1 */) {
// after the acquire, B is guaranteed to see D
}
Here fetch_add (an RMW) or an acquire load that reads from the release sequence establishes synchronization so B sees D. [1][2]
References
- memory_order (cppreference) — release/acquire/acq_rel and release‑acquire semantics. [1]
- C++ standard / WG21 paper describing release sequence and synchronizes‑with (intro.races / release sequence). [2]
- Discussion and examples (explain subtleties about which operations extend a release sequence). [3]
Synchronization data race: non-winner completion breaks release-acquire chain for winner state.
When a non-winner is the last task to complete, result_ and winner_exception_ (non-atomic) are read without synchronization. The remaining_count_.fetch_sub(acq_rel) extends the release sequence, but since the non-winner does not write these fields, the parent coroutine's resumption via dispatch() lacks explicit acquire semantics at the C++ level. Introduce winner_ready_ atomic, set with release after storing winner result/exception/void, and acquire it before reading in when_any. Update the memory-model comment at lines 82-87 to reflect this requirement.
🧵 Suggested fix (read side)
- if(state.winner_exception_)
+ state.winner_ready_.load(std::memory_order_acquire);
+ if(state.winner_exception_)
std::rethrow_exception(state.winner_exception_);
co_return result_type{state.winner_index_, std::move(*state.result_)};🧵 Suggested fix (state side)
struct when_any_state
{
+ std::atomic<bool> winner_ready_{false};
...
template<typename T>
void set_winner_result(T value)
noexcept(std::is_nothrow_move_constructible_v<T>)
{
result_.emplace(std::in_place_type<T>, std::move(value));
+ winner_ready_.store(true, std::memory_order_release);
}
void set_winner_void() noexcept
{
result_.emplace(std::in_place_type<std::monostate>, std::monostate{});
+ winner_ready_.store(true, std::memory_order_release);
}
void set_winner_exception(std::exception_ptr ep) noexcept
{
winner_exception_ = ep;
+ winner_ready_.store(true, std::memory_order_release);
}
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if(state.winner_exception_) | |
| std::rethrow_exception(state.winner_exception_); | |
| co_return result_type{state.winner_index_, std::move(*state.result_)}; | |
| state.winner_ready_.load(std::memory_order_acquire); | |
| if(state.winner_exception_) | |
| std::rethrow_exception(state.winner_exception_); | |
| co_return result_type{state.winner_index_, std::move(*state.result_)}; |
🤖 Prompt for AI Agents
In `@include/boost/capy/when_any.hpp` around lines 822 - 825, Introduce an atomic
flag winner_ready_ in the shared state and use it to establish a release-acquire
synchronization: when you nominate a winner (the code path that writes
state.result_, state.winner_exception_, and state.winner_index_ and then calls
dispatch()), store winner_ready_.store(true, std::memory_order_release)
immediately after those non-atomic writes; on the when_any read side (inside
when_any before reading state.result_ or state.winner_exception_), perform
if(!winner_ready_.load(std::memory_order_acquire)) then wait/loop or otherwise
ensure acquisition before accessing those fields so the reads see the published
winner data even if a non-winner performed the final
remaining_count_.fetch_sub(acq_rel); also update the existing memory-model
comment (around the previous lines 82–87) to document this release on winner
write and acquire on winner read and why it is needed for correct
synchronization with remaining_count_.fetch_sub(acq_rel), referencing
remaining_count_.fetch_sub(acq_rel), dispatch(), when_any, winner_ready_,
state.result_, state.winner_exception_, and winner_index_.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude disagrees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mvandeberg Could you share Claude's specific reasoning? I'd like to understand the disagreement to provide a more targeted analysis.
Here's my understanding of the synchronization concern:
The Question: When the parent coroutine reads result_ and winner_exception_, are those reads guaranteed to see the winner's writes?
The Synchronization Chain:
- Winner task writes to
result_/winner_exception_(non-atomic) - Winner calls
signal_completion()→fetch_sub(acq_rel)onremaining_count_ - Last task (possibly different) calls
signal_completion()→fetch_sub(acq_rel)sees count==1 - Last task dispatches parent via
caller_ex_.dispatch(continuation_) - Parent resumes and reads
result_/winner_exception_
Key Point: When a release and acquire synchronize on the same atomic, "all memory writes (including non-atomic and relaxed atomic) that happened-before the atomic store from the point of view of thread A, become visible side-effects in thread B" if "B actually returns the value that A stored, or a value from later in the release sequence."
The winner's fetch_sub(release) happens before the last task's fetch_sub(acquire) (guaranteed by counter semantics). A "release sequence" is formed when "some atomic is store-released and several other threads perform read-modify-write operations on that atomic", which should establish synchronization.
However, the concern is whether this synchronization transitively reaches the parent through dispatch(). If the executor's dispatch() doesn't provide its own memory ordering guarantees, there might be a gap between the last task's synchronization point and the parent's actual execution.
Would adding explicit synchronization via winner_ready_ be overly conservative, or is it necessary for portability across all executor implementations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Synchronization analysis:
- All fetch_sub operations on remaining_count_ form a release sequence
- Winner's fetch_sub releases; subsequent fetch_sub operations participate in the modification order of remaining_count_
- Last task's fetch_sub(acq_rel) synchronizes-with prior releases in the modification order, establishing happens-before from winner's writes
- Executor dispatch() is expected to provide queue-based synchronization (release-on-post, acquire-on-execute) completing the chain to parent
- Even inline executors work (same thread = sequenced-before)
Alternative considered: Adding winner_ready_ atomic (set with release after storing winner data, acquired before reading) would make synchronization self-contained and not rely on executor implementation details. Current approach is correct but requires careful reasoning about release sequences and executor behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mvandeberg Thank you for sharing Claude's detailed analysis. After reviewing it carefully, I agree that the synchronization is sound.
Why the current approach is correct:
-
Release sequence semantics: The winner's
fetch_sub(acq_rel)releases the non-atomic writes toresult_/winner_exception_. Subsequentfetch_suboperations (including by non-winners) form a release sequence onremaining_count_. -
Transitive synchronization: When the last task calls
fetch_sub(acq_rel)and seesremaining == 1, it synchronizes-with all prior releases in the modification order, including the winner's. This establishes the happens-before relationship from the winner's writes. -
Executor guarantees: The
dispatch()call is reasonably expected to provide queue-based synchronization (release-on-post, acquire-on-execute), completing the chain to the parent coroutine. For inline executors, same-thread execution provides sequenced-before ordering.
My initial concern was overly conservative. The pattern relies on well-understood release sequence behavior plus standard executor semantics, both of which are solid foundations.
Claude's note about the winner_ready_ alternative making synchronization self-contained is valid—it would eliminate dependency on executor implementation details—but given that proper memory ordering is a fundamental requirement for any executor, the current approach is both correct and appropriately efficient.
🐰
✏️ Learnings added
Learnt from: mvandeberg
Repo: cppalliance/capy PR: 101
File: include/boost/capy/when_any.hpp:842-845
Timestamp: 2026-01-22T18:13:22.758Z
Learning: In when_any implementations using atomic remaining_count_, the winner's fetch_sub(acq_rel) releases non-atomic result/exception writes, and the last task's fetch_sub(acq_rel) acquires via release sequence semantics, establishing happens-before for the parent's reads when combined with executor dispatch() synchronization guarantees.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
8107c6d to
f86a1c6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@include/boost/capy/when_any.hpp`:
- Around line 985-989: The noexcept on set_winner_result(T value) is wrong:
replace the current noexcept condition that references
is_nothrow_move_assignable_v<std::optional<T>> with a check for
is_nothrow_move_constructible_v<T> (matching the heterogeneous overload and the
fact you call result_.emplace(std::move(value))). Update the noexcept specifier
on the set_winner_result function to use std::is_nothrow_move_constructible_v<T>
so the exception guarantee correctly reflects constructing the stored T.
3f2285c to
2f9d4fb
Compare
|
@coderabbitai Do not consider when_all.hpp for docstring coverage of this PR. |
|
Tip For best results, initiate chat on the files or code changes.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
| fetch_from_primary(), | ||
| fetch_from_backup() | ||
| ); | ||
| // index is 0 or 1 (which task won) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If result is a variand, it already contains the index, and the index is redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only the case if each task has a different return type. As soon as tasks share return types, the index is required to know which task completed.
…embers, and methods
2f9d4fb to
c426e64
Compare
c426e64 to
0bfcedd
Compare
Closes #98
Summary by CodeRabbit
New Features
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.