Skip to content

Commit 70fa90d

Browse files
authored
rebase the P3941 wording on P3826r3 (#72)
* rebase the P3941 wording on P3826r3 - add get_completion_domain in the quoted synopsis - change SCHED-ENV to use get_start_scheduler instead of get_scheduler - remove the write_env use from on (should have been done by P3826r3) - change on to use get_start_scheduler instead of get_scheduler - update the sentence for make-sender(affine_on...) - change affine_on to use continues_on instead of schedule_from - update the reference paragraph numbers in [exec.run.loop.types] * fixed typos noted by the AI (the other changes AI comments on are intentional) * applied enhancements/fixes suggested by Eric * minor fix * fixed a few minor issues with the wording * change as_awaitable to transform the sender if necessary * changed affine_on to use UNSTOPPABLE-SCHEDULER. * fixed CI issues and some feedback on the wording * fix another CI issu * another error discovered by CI * another attempt to get past the CI
1 parent 21be211 commit 70fa90d

2 files changed

Lines changed: 165 additions & 111 deletions

File tree

docs/P3941-affinity.md

Lines changed: 154 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Scheduler Affinity
3-
document: P3941R2
4-
date: 2026-02-23
3+
document: P3941R3
4+
date: 2026-03-21
55
audience:
66
- Concurrency Working Group (SG1)
77
- Library Evolution Working Group (LEWG)
@@ -31,6 +31,12 @@ meet its objective at run-time.
3131

3232
# Change History
3333

34+
## R3
35+
36+
- rebase changes on the customization changes [P3826r3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html)
37+
- use `transform_sender` in `as_awaitable` to locate possible customization of nested
38+
senders in [[exec.as.awaitable](https://wg21.link/exec.as.awaitable#7)]
39+
3440
## R2
3541

3642
- added requirement on `get_scheduler`/`get_start_scheduler`
@@ -549,6 +555,13 @@ algorithm a better name.
549555
550556
# Wording Changes
551557
558+
::: ednote
559+
This wording is relative to [N5032](https://wg21.link/N5032) with
560+
the changes from
561+
[P3826r3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html)
562+
applied.
563+
:::
564+
552565
::: ednote
553566
If `get_start_scheduler` is introduced add it to the synopsis in
554567
[execution.syn] after `get_scheduler` as follows:
@@ -565,6 +578,8 @@ namespace std::execution {
565578
struct get_forward_progress_guarantee_t { @_unspecified_@ };
566579
template<class CPO>
567580
struct get_completion_scheduler_t { @_unspecified_@ };
581+
template<class CPO = void>
582+
struct get_completion_domain_t { @_unspecified_@ };
568583
struct get_await_completion_adaptor_t { @_unspecified_@ };
569584

570585
inline constexpr get_domain_t get_domain{};
@@ -575,6 +590,8 @@ namespace std::execution {
575590
inline constexpr get_forward_progress_guarantee_t get_forward_progress_guarantee{};
576591
template<class CPO>
577592
constexpr get_completion_scheduler_t<CPO> get_completion_scheduler{};
593+
template<class CPO = void>
594+
constexpr get_completion_domain_t<CPO> get_completion_domain{};
578595
inline constexpr get_await_completion_adaptor_t get_await_completion_adaptor{};
579596
...
580597
}
@@ -609,55 +626,62 @@ expression `get_start_scheduler(get_env(rcvr))` is well-formed, an operation
609626
state that is the result of calling `connect(sndr, rcvr)` shall, if
610627
it is started, be started on an execution agent associated with the
611628
scheduler `get_start_scheduler(get_env(rcvr))`.
629+
:::
630+
631+
::: ednote
632+
633+
If `get_start_scheduler` is introduced change
634+
[[exec.snd.expos](https://wg21.link/exec.snd.expos)] paragraph 8 to have <code><i>SCHED-ENV</i></code>
635+
use `get_start_scheduler` instead of `get_scheduler`:
612636
613637
:::
614638
639+
[8]{.pnum} <code><i>SCHED-ENV</i>(sch)</code> is an expression `o2` whose type
640+
satisfies <code><i>queryable</i></code> such that `o2.query(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@)`
641+
is a prvalue with the same type and value as `sch`, and such that
642+
`o2.query(get_domain)` is expression-equivalent to `sch.query(get_domain)`.
643+
644+
615645
::: ednote
616-
If `get_start_scheduler` is introduced change how `on` gets its
617-
scheduler in [exec.on], i.e., change the use from `get_scheduler`
618-
to use `get_start_scheduler`:
646+
The specification of `on` [[exec.on](https://wg21.link/exec.on)] shouldn't use `write_env` as it does,
647+
i.e., this change removes these (not removing them was an oversight
648+
in
649+
[P3826](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html)).
650+
In addition, if `get_start_scheduler` is introduced change how `on`
651+
gets its scheduler in [[exec.on](https://wg21.link/exec.on)], i.e., change the use from
652+
`get_scheduler` to use `get_start_scheduler`:
619653
:::
620654
621655
<p>
622656
...
623657
</p>
624658
625659
<p>
626-
The expression `on.transform_sender(out_sndr, env)` has effects equivalent to:
660+
[8]{.pnum} Otherwise, the expression `on.transform_sender(set_value, out_sndr, env)` has effects equivalent to:
627661
</p>
628662
629663
```
630664
auto&& [_, data, child] = out_sndr;
631665
if constexpr (scheduler<decltype(data)>) {
632666
auto orig_sch =
633-
@_query-with-default_@(get_@[start_]{.add}@scheduler, env, @_not-a-scheduler_@());
634-
635-
if constexpr (same_as<decltype(orig_sch), @_not-a-scheduler_@>) {
636-
return @_not-a-sender_@{};
637-
} else {
638-
return continues_on(
639-
starts_on(std::forward_like<OutSndr>(data), std::forward_like<OutSndr>(child)),
640-
std::move(orig_sch));
641-
}
667+
@_call-with-default_@(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@, @_not-a-scheduler_@(), env);
668+
669+
return continues_on(
670+
starts_on(std::forward_like<OutSndr>(data), std::forward_like<OutSndr>(child)),
671+
std::move(orig_sch));
642672
} else {
643673
auto& [sch, closure] = data;
644-
auto orig_sch = @_query-with-default_@(
645-
get_completion_scheduler<set_value_t>,
646-
get_env(child),
647-
@_query-with-default_@(get_@[start_]{.add}@scheduler, env, @_not-a-scheduler_@()));
648-
649-
if constexpr (same_as<decltype(orig_sch), @_not-a-scheduler_@>) {
650-
return @_not-a-sender_@{};
651-
} else {
652-
return write_env(
653-
continues_on(
654-
std::forward_like<OutSndr>(closure)(
655-
continues_on(
656-
write_env(std::forward_like<OutSndr>(child), @_SCHED-ENV_@(orig_sch)),
657-
sch)),
658-
orig_sch),
659-
@_SCHED-ENV_@(sch));
660-
}
674+
auto orig_sch = @_call-with-default_@(
675+
get_completion_scheduler<set_value_t>, @_not-a-scheduler_@(), get_env(child), env);
676+
677+
return continues_on(
678+
@[write_env(]{.rm}@
679+
std::forward_like<OutSndr>(closure)(
680+
continues_on(
681+
@[write_env(]{.rm}@std::forward_like<OutSndr>(child)@[,]{.rm}@ @[_SCHED-ENV_(orig_sch))]{.rm}@,
682+
sch)),
683+
@[_SCHED-ENV_(sch)),]{.rm}@
684+
orig_sch);
661685
}
662686
```
663687
<p>
@@ -748,6 +772,71 @@ scheduler `get_scheduler(get_env(rcvr))`.
748772
749773
:::
750774
775+
::: ednote
776+
777+
Change [[exec.as.awaitable](https://wg21.link/exec.as.awaitable#7)]
778+
paragraph 7 such that it tries to locate a customization for
779+
`as_awaitable` on the transformed nested sender.
780+
781+
:::
782+
783+
[7]{.pnum} `as_awaitable` is a customization point object. For
784+
subexpressions `expr` and `p` where `p` is an lvalue, `Expr` names
785+
the type `decltype((expr))` and `Promise` names the type
786+
`decay_t<decltype((p))>, as_awaitable(expr, p)` is expression-equivalent
787+
to, except that the evaluations of `expr` and `p` are indeterminately
788+
sequenced:
789+
790+
<ul>
791+
<li>
792+
<p>[7.1]{.pnum}
793+
`expr.as_awaitable(p)` if that expression is well-formed.
794+
</p>
795+
<p>
796+
Mandates: `@_is-awaitable_@<A, Promise>` is `true`, where `A` is
797+
the type of the expression above.
798+
</p>
799+
</li>
800+
<li>
801+
802+
[7.?]{.pnum}
803+
804+
[`@_adapt-for-await-completion_@(transform_sender(expr, get_env(p))).as_awaitable(p)`
805+
if this expression is well-formed, `sender_in<Expr, env_of_t<Promise>>` is `true`,
806+
and `@_single-sender-value-type_@<Expr, env_of_t<Promise>>` is well-formed.]{.add}
807+
808+
</li>
809+
<li>
810+
[7.2]{.pnum} Otherwise, `(void(p), expr)` if
811+
`decltype(@_GET-AWAITER_@(expr))` satisfies `@_is-awaiter_@<Promise>`.
812+
</li>
813+
<li>
814+
<p>
815+
[7.3]{.pnum} [Otherwise, `@_sender-awaitable_@{@_adapted-expr_@, p}` if]{.rm}
816+
</p>
817+
<p>[`@_has-queryable-await-completion-adaptor_@<Expr>`]{.rm}</p>
818+
<p>[and]{.rm}</p>
819+
<p>[`@_awaitable-sender_@<decltype((@_adapted-expr_@)), Promise>`]{.rm}</p>
820+
<p>
821+
[are both satisfied, where `@_adapted-expr_@` is
822+
`get_await_completion_adaptor(get_env(expr))(expr)`, except that
823+
`expr` is evaluated only once.]{.rm}
824+
</p>
825+
</li>
826+
<li>
827+
[7.4]{.pnum} Otherwise, [`@_sender-awaitable_@{expr, p}` if
828+
`@_awaitable-sender_@<Expr, Promise>` is `true`.]{.rm}
829+
[`@_sender-awaitable_@{@_adapt-for-await-completion_@(transform_sender(expr, get_env(p))), P}` if `sender_in<Expr, env_of_t<Promise>>` is `true` and `@_single-sender-value-type_@<Expr, env_of_t<Promise>>` is well-formed]{.add}
830+
831+
</li>
832+
<li>[7.5]{.pnum} Otherwise, `(void(p), expr)`.
833+
</li>
834+
</ul>
835+
836+
[8]{.pnum} [The expression `@_adapt-for-await-completion_@(s)` is
837+
`get_await_completion_adaptor(get_env(s))(s)` if that is well-formed,
838+
and `s` otherwise.]{.add}
839+
751840
::: ednote
752841
Change [exec.affine.on] to use only one parameter, require an
753842
infallible scheduler from the receiver, and add a default implementation
@@ -770,26 +859,8 @@ satisfy sender, <code>affine_on(sndr[, sch]{.rm})</code> is ill-formed.
770859
771860
[3]{.pnum}
772861
Otherwise, the expression <code>affine_on(sndr[, sch]{.rm})</code>
773-
is expression-equivalent to:
774-
<code>transform_sender(_get-domain-early_(sndr), _make-sender_(affine_on,
775-
[sch]{.rm}[env&lt;&gt;()]{.add}, sndr))</code> except that `sndr`
776-
is evaluated only once.
777-
778-
[4]{.pnum}
779-
The exposition-only class template <code>_impls-for_</code>
780-
([exec.snd.expos]) is specialized for `affine_on_t` as follows:
781-
782-
```c++
783-
namespace std::execution {
784-
template<>
785-
struct impls-for<affine_on_t> : default-impls {
786-
static constexpr auto get-attrs =
787-
[](const auto&@[ data]{.rm}]@, const auto& child) noexcept -> decltype(auto) {
788-
return @[_JOIN-ENV_(_SCHED-ATTRS_(data),_FWD-ENV_(]{.rm}@get_env(child)@[))]{.rm}@;
789-
};
790-
};
791-
}
792-
```
862+
is expression-equivalent to
863+
`@_make-sender_@(affine_on, @[sch]{.rm}@@[env&lt;&gt;()]{.add}@, sndr)`.
793864
794865
:::{.add}
795866
[?]{.pnum}
@@ -805,18 +876,19 @@ using child_tag_t = tag_of_t<remove_cvref_t<decltype(child)>>;
805876
if constexpr (requires(const child_tag_t& t){ t.affine_on(child, ev); })
806877
return t.affine_on(child, ev);
807878
else
808-
return write_env(
809-
schedule_from(get_start_scheduler(get_env(ev)), write_env(std::move(child), ev)),
810-
JOIN-ENV(env{prop{get_stop_token, never_stop_token()}}, ev)
811-
);
879+
return continues_on(child, @_UNSTOPPABLE-SCHEDULER_@(get_start_scheduler(ev)));
812880
```
813881
814-
[Note 1: This causes the `affine_on(sndr)` sender to become
815-
`schedule_from(sch, sndr)` when it is connected with a receiver
816-
`rcvr` whose execution domain does not customize `affine_on`,
817-
for which `get_start_scheduler(get_env(rcvr))` is `sch`, and `affine_on`
818-
isn't specialized for the child sender.
819-
end note]
882+
[?]{.pnum} For a subexpression `sch` whose type satisfies `scheduler`,
883+
let `@_UNSTOPPABLE-SCHEDULER_@(sch)` be an expression `e` whose type
884+
satisfies `scheduler` such that:
885+
<ul>
886+
<li>[?.1]{.pnum} `schedule(e)` is expression-equivalent to `unstoppable(schedule(sch))`.</li>
887+
<li>[?.2]{.pnum} For any query object `q` and pack of subexpressions `args...`, `e.query(q, args...)`
888+
is expression-equivalent to `sch.query(q, args...)`.</li>
889+
<li>[?.3]{.pnum} Let `f` be the subexpression `@_UNSTOPPABLE-SCHEDULER_@(other)`. `e == f`
890+
is expression-equivalent to `sch == other`.</li>
891+
</ul>
820892
821893
[?]{.pnum}
822894
_Recommended Practice_: Implementations should provide `affine_on`
@@ -875,26 +947,8 @@ satisfy sender, <code>affine_on(sndr[, sch]{.rm})</code> is ill-formed.
875947
876948
[3]{.pnum}
877949
Otherwise, the expression <code>affine_on(sndr[, sch]{.rm})</code>
878-
is expression-equivalent to:
879-
<code>transform_sender(_get-domain-early_(sndr), _make-sender_(affine_on,
880-
[sch]{.rm}[env&lt;&gt;()]{.add}, sndr))</code> except that `sndr`
881-
is evaluated only once.
882-
883-
[4]{.pnum}
884-
The exposition-only class template <code>_impls-for_</code>
885-
([exec.snd.expos]) is specialized for `affine_on_t` as follows:
886-
887-
```c++
888-
namespace std::execution {
889-
template<>
890-
struct impls-for<affine_on_t> : default-impls {
891-
static constexpr auto get-attrs =
892-
[](const auto&@[ data]{.rm}]@, const auto& child) noexcept -> decltype(auto) {
893-
return @[_JOIN-ENV_(_SCHED-ATTRS_(data),_FWD-ENV_(]{.rm}@get_env(child)@[))]{.rm}@;
894-
};
895-
};
896-
}
897-
```
950+
is expression-equivalent to
951+
`@_make-sender_@(affine_on, @[sch]{.rm}@@[env&lt;&gt;()]{.add}@, sndr)`.
898952
899953
:::{.add}
900954
[?]{.pnum}
@@ -910,18 +964,19 @@ using child_tag_t = tag_of_t<remove_cvref_t<decltype(child)>>;
910964
if constexpr (requires(const child_tag_t& t){ t.affine_on(child, ev); })
911965
return t.affine_on(child, ev);
912966
else
913-
return write_env(
914-
schedule_from(get_scheduler(get_env(ev)), write_env(std::move(child), ev)),
915-
JOIN-ENV(env{prop{get_stop_token, never_stop_token()}}, ev)
916-
);
967+
return continues_on(child, @_UNSTOPPABLE-SCHEDULER_@(get_scheduler(ev)));
917968
```
918969
919-
[Note 1: This causes the `affine_on(sndr)` sender to become
920-
`schedule_from(sch, sndr)` when it is connected with a receiver
921-
`rcvr` whose execution domain does not customize `affine_on`,
922-
for which `get_scheduler(get_env(rcvr))` is `sch`, and `affine_on`
923-
isn't specialized for the child sender.
924-
end note]
970+
[?]{.pnum} For a subexpression `sch` whose type satisfies `scheduler`,
971+
let `@_UNSTOPPABLE-SCHEDULER_@(sch)` be an expression `e` whose type
972+
satisfies `scheduler` such that:
973+
<ul>
974+
<li>[?.1]{.pnum} `schedule(e)` is expression-equivalent to `unstoppable(schedule(sch))`.</li>
975+
<li>[?.2]{.pnum} For any query object `q` and pack of subexpressions `args...`, `e.query(q, args...)`
976+
is expression-equivalent to `sch.query(q, args...)`.</li>
977+
<li>[?.3]{.pnum} Let `f` be the subexpression `@_UNSTOPPABLE-SCHEDULER_@(other)`. `e == f`
978+
is expression-equivalent to `sch == other`.</li>
979+
</ul>
925980
926981
[?]{.pnum}
927982
_Recommended Practice_: Implementations should provide `affine_on`
@@ -1065,9 +1120,9 @@ explicit task_scheduler(Sch&& sch, Allocator alloc = {});
10651120
10661121
::: add
10671122
[?]{.pnum}
1068-
_Mandates_: Let `e` be an environment and let `E` be `decltype(e)`.
1069-
If `unstoppable_token<decltype(get_stop_token(e))>` is `true`, then
1070-
the type `completion_signatures_of_t<decltype(schedule(sch)), E>`
1123+
_Mandates_: Let `E` be the type of a queryable.
1124+
If `unstoppable_token<stop_token_of_t<E>>` is `true`, then
1125+
the type `completion_signatures_of_t<schedule_result_t<Sch>, E>`
10711126
only includes `set_value_t()`, otherwise it may additionally include
10721127
`set_stopped_t()`.
10731128
:::
@@ -1105,7 +1160,7 @@ namespace std::execution {
11051160
<code><i>ts-sender</i></code> is an exposition-only class that
11061161
models `sender` ([exec.snd]) and for which
11071162
<code>completion_signatures_of_t&lt;<i>ts-sender</i>[, E]{.add}&gt;</code>
1108-
denotes[:]{.rm}[ `completion_signatures<set_value_t()>` if `unstoppable_token<decltype(get_stop_token(declval<E>()))>` is `true`, and
1163+
denotes[:]{.rm}[ `completion_signatures<set_value_t()>` if `unstoppable_token<stop_token_of_t<E>>` is `true`, and
11091164
otherwise `completion_signatures<set_value_t(), set_stopped_t()>`.]{.add}
11101165
11111166
::: rm
@@ -1128,9 +1183,9 @@ In [exec.run.loop.types] change the paragraph defining the completion signatures
11281183
class run-loop-sender;
11291184
```
11301185
1131-
[5]{.pnum}
1186+
[6]{.pnum}
11321187
<code><i>run-loop-sender</i></code> is an exposition-only type that satisfies `sender`.
1133-
[Let `E` be the type of an environment. If `unstoppable_token<decltype(get_stop_token(declval<E>()))>` is `true`,
1188+
[Let `E` be the type of an environment. If `unstoppable_token<stop_token_of_t<E>>` is `true`,
11341189
then ]{.add} <code>completion_signatures_of_t&lt;<i>run-loop-sender</i>[, E]{.add}&gt;</code> is
11351190
11361191
::: rm
@@ -1149,7 +1204,7 @@ Otherwise it is
11491204
```
11501205
:::
11511206
1152-
[6]{.pnum} An instance of <code><i>run-loop-sender</i></code> remains
1207+
[7]{.pnum} An instance of <code><i>run-loop-sender</i></code> remains
11531208
valid until the end of the lifetime of its associated `run_loop`
11541209
instance.
11551210

0 commit comments

Comments
 (0)