| Doc. no. | P4258R0 |
| Date: | 2026-06-08 |
| Audience: | WG21 |
| Reply to: | Jonathan Wakely <lwgchair@gmail.com> |
packaged_task::operator=
should abandon its shared state
NULL is too broad
basic_const_iterator's relational operators due to ADL + CWG 2369
lazy_split_view
as_awaitable(expr, p) does not define semantics of call if p is not an lvalue
std::begin/end
get_env noexcept-ness from inline_scheduler
is_steady of a
Cpp17Clock
type required to be usable in constant expressions?
std::ranges::size in ranges algorithms
ranges::to
constructs associative containers via
c.emplace(c.end(), *it)
std::to_(w)string?
apply_result, is_applicable
std::execution::spawn_future is mishandling dependent senders
Env which is not in scope
volatile atomic deprecationsSection: 32.5.8.2 [atomics.types.operations] Status: Voting Submitter: Alisdair Meredith Opened: 2020-03-19 Last modified: 2026-04-14
Priority: 3
View all other issues in [atomics.types.operations].
Discussion:
Paper P1831R1 aimed to deprecate all
the atomic overloads for volatile qualified member function of
std::atomic unless is_always_lock_free is true.
There are a few omissions in the wording.
operator++ and operator-- are correctly constrained,
but the deprecated overloads are not restored in Annex D, unlike the other
member functions. I confirmed with the paper author this is an accidental
oversight, and not an intended change of behavior for C++20.
Secondly, the wait/notify APIs were added after the initial wording
For this paper was drafted, and the paper did not catch up. Again, I
confirmed with the paper author that these functions should be
similarly constrained and deprecated.
[2020-04-04 Issue Prioritization]
Priority to 3 after reflector discussion. The suggested wording was generally accepted, but there
were considerable doubts that it is necessary to add deprecated functions of the new wait/notify
functions instead of declaring only the non-volatile overloads. The wish was expressed that both SG1 and
LEWG should express their opinion here.
[Kona 2025-11-05; SG1 had strong consensus for the proposed resolution. Status changed: SG1 → Open.]
[2026-04-14; Approved by LWG in Kona, but not added to the plenary polls. Status changed: Open → Ready.]
Proposed resolution:
This wording is relative to N4861.
Modify 32.5.8.2 [atomics.types.operations] as indicated:
void wait(T old, memory_order order = memory_order::seq_cst) const volatile noexcept; void wait(T old, memory_order order = memory_order::seq_cst) const noexcept;Constraints: For the
-29- Preconditions: […] […]volatileoverload of this function,is_always_lock_freeistrue.void notify_one() volatile noexcept; void notify_one() noexcept;Constraints: For the
-32- Effects: […] […]volatileoverload of this function,is_always_lock_freeistrue.void notify_all() volatile noexcept; void notify_all() noexcept;Constraints: For the
-34- Effects: […] […]volatileoverload of this function,is_always_lock_freeistrue.
Modify D.24.2 [depr.atomics.volatile], annex D, as indicated:
If an atomic specialization has one of the following overloads, then that overload participates in overload resolution even if
atomic<T>::is_always_lock_freeisfalse:void store(T desired, memory_order order = memory_order::seq_cst) volatile noexcept; […] T* fetch_key(ptrdiff_t operand, memory_order order = memory_order::seq_cst) volatile noexcept; value_type operator++(int) volatile noexcept; value_type operator--(int) volatile noexcept; value_type operator++() volatile noexcept; value_type operator--() volatile noexcept; void wait(T old, memory_order order = memory_order::seq_cst) const volatile noexcept; void notify_one() volatile noexcept; void notify_all() volatile noexcept;
move_iterator's default constructor should be constrainedSection: 24.5.4.2 [move.iterator], 24.5.4.4 [move.iter.cons] Status: Voting Submitter: Hewill Kang Opened: 2024-07-22 Last modified: 2026-05-30
Priority: 3
View other active issues in [move.iterator].
View all other issues in [move.iterator].
Discussion:
Although it is unclear why P2325 did not apply to move_iterator, there is
implementation divergence among the current libraries (demo):
#include <istream>
#include <iterator>
#include <ranges>
using R = std::ranges::istream_view<int>;
using I = std::ranges::iterator_t<R>;
using MI = std::move_iterator<I>;
static_assert(std::default_initializable<MI>); // libstdc++ passes, libc++ fails
As libc++ additionally requires that its default constructors satisfy is_constructible_v<Iterator>.
move_iterator
only requires the underlying iterator to be input_iterator which may not be default_initializable.
[2024-08-02; Reflector poll]
Set priority to 3 after reflector poll.
Six P0 (tentatively ready) votes but one for NAD:
"design change, needs paper. Not constraining this seems to be intended by
P2325R3. Even if we want to constrain it,
why default_initializable rather than just is_constructible_v?"
Author of P2325R3 had no such intent and voted P0.
Issue submitter used default_initializable to align with forward_iterator
requirements, but would not object to implementers using is_constructible_v
for backported DRs.
[2026-05-29 LWG telecon; status: New → Ready]
Proposed resolution:
This wording is relative to N4986.
Modify 24.5.4.2 [move.iterator] as indicated:
namespace std {
template<class Iterator>
class move_iterator {
public:
[…]
constexpr move_iterator() requires default_initializable<Iterator> = default;
[…]
private:
Iterator current = Iterator(); // exposition only
};
}
Modify 24.5.4.4 [move.iter.cons] as indicated:
constexpr move_iterator();
-1- Effects: Value-initializescurrent.
packaged_task::operator= should abandon its shared stateSection: 32.10.10.2 [futures.task.members] Status: Voting Submitter: Jonathan Wakely Opened: 2024-09-19 Last modified: 2026-05-19
Priority: 3
View all other issues in [futures.task.members].
Discussion:
The packaged_task move assignment operator is specified to release
the previous shared state. This means it releases ownership, but does
not make the shared state ready. Any future that shares ownership of the
shared state will never receive a result, because the provider is gone.
This means that any thread that waits on the future will block forever.
There is a note on packaged_task::reset() which claims that assignment
abandons the state, which is not supported by any normative wording:
void reset();-26- Effects: As if
*this = packaged_task(std::move(f)), wherefis the task stored in*this.[Note 2: This constructs a new shared state for
*this. The old state is abandoned (32.10.5 [futures.state]). — end note]
Presumably, the intended behaviour of assignment was to abandon the
shared state,
i.e. make it ready with a broken_promise error, and then release it.
That is what the std::promise move assignment does
(see 32.10.6 [futures.promise] p9).
Both libstdc++ and libc++ abandon the state, despite what the standard says.
[2024-10-02; Reflector poll]
Set priority to 3 after reflector poll.
This wording is relative to N4988.
Modify 32.10.10.2 [futures.task.members] as indicated:
packaged_task& operator=(packaged_task&& rhs) noexcept;-11- Effects:
- (11.1) —
ReleasesAbandons any shared state (32.10.5 [futures.state]);- (11.2) — calls
packaged_task(std::move(rhs)).swap(*this).-?- Returns:
*this.
[2024-10-02; Jonathan provides improved wording]
Following reflector discussion, remove the "Releases any shared state" text completely. The remaining effects imply that the state will be abandoned anyway. This makes it safe against self-assignment.
[2024-10-02; LWG telecon]
Agreed to change promise the same way, which is safe for self-assignment
and matches what all three of libstdc++, libc++ and MSVC do today.
Ask SG1 to review.
[2026-05-19; LWG telecon. Status changed: New → Ready.]
SG1 agreed with the resolution. LWG moved to Ready.
Proposed resolution:
This wording is relative to N4988.
Modify 32.10.6 [futures.promise] as indicated:
promise& operator=(promise&& rhs) noexcept;-9- Effects:
Abandons any shared state (32.10.5 [futures.state]) and then as ifEquivalent topromise(std::move(rhs)).swap(*this).-10- Returns:
*this.
Modify 32.10.10.2 [futures.task.members] as indicated:
packaged_task& operator=(packaged_task&& rhs) noexcept;-11- Effects:
(11.1) — Releases any shared state (32.10.5 [futures.state]);(11.2) — callsEquivalent topackaged_task(std::move(rhs)).swap(*this).-?- Returns:
*this.
NULL is too broadSection: 17.2.3 [support.types.nullptr] Status: Voting Submitter: Janet Cobb Opened: 2024-12-09 Last modified: 2026-05-30
Priority: 3
Discussion:
7.3.12 [conv.ptr]/1 reads in part: "A null pointer constant is an integer literal (5.13.2 [lex.icon])
with value zero or a prvalue of type std::nullptr_t.".
NULL is an implementation-defined null pointer constant.".
Together, these imply that #define NULL (::std::unreachable(), nullptr) is a conforming definition. The expression is
a prvalue of type std::nullptr_t, so it is a null pointer constant. This makes it implementation-defined whether
any program that evaluates NULL has undefined behavior.
[2025-02-07; Reflector poll]
Set priority to 3 after reflector poll.
"I'd very much like to see nullptr added to the footnote."
This wording is relative to N5001.
Modify 17.2.3 [support.types.nullptr] as indicated:
-2- The macro
161) Possible definitions includeNULLis an implementation-defined null pointer constant that is a literal (5.13.2 [lex.icon], 5.13.8 [lex.nullptr]).footnote 1610and0L, but not(void*)0.
[2025-02-07; Jonathan provides improved wording]
[2026-05-29 LWG telecon; status: New → Ready]
Proposed resolution:
This wording is relative to N5001.
Modify 17.2.3 [support.types.nullptr] as indicated:
-2- The macro
161) Possible definitions includeNULLis an implementation-defined null pointer constant that is a literal (5.13.2 [lex.icon], 5.13.8 [lex.nullptr]).footnote 161nullptr,0and0L, but not(void*)0.
basic_const_iterator's relational operators due to ADL + CWG 2369Section: 24.5.3.5 [const.iterators.ops] Status: Voting Submitter: Patrick Palka Opened: 2025-03-03 Last modified: 2026-05-19
Priority: 2
Discussion:
Consider the example (devised by Hewill Kang)
using RCI = reverse_iterator<basic_const_iterator<vector<int>::iterator>>; static_assert(std::totally_ordered<RCI>);
Checking RCI is totally_ordered entails checking
requires (RCI x) { x RELOP x; } for each RELOP in {<, >, <=, >=}
which we expect to be straightforwardly satisfied by reverse_iterator's
namespace-scope operators (24.5.1.8 [reverse.iter.cmp]):
template<class Iterator1, class Iterator2>
constexpr bool operator<(
const reverse_iterator<Iterator1>& x,
const reverse_iterator<Iterator2>& y);
// etc
But due to ADL we find ourselves also considering the basic_const_iterator
relop friends (24.5.3.5 [const.iterators.ops]/24).
template<input_iterator Iterator>
class basic_const_iterator {
template<not-a-const-iterator I>
friend constexpr bool operator<(const I& x, const basic_const_iterator& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>
// etc
};
Before CWG 2369 these candidates would quickly get
discarded since for the second operand RCI clearly isn't convertible to basic_const_iterator.
But after CWG 2369 implementations must first check these operators' constraints
(with Iterator = vector<int>::iterator and I = RCI), which entails
checking totally_ordered<RCI> recursively, causing the example to be ill-formed.
basic_const_iterator<J> where J is
constrained to match Iterator:
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator<(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>
// etc
So that deduction fails earlier, before constraints get checked, for a second
operand that isn't a specialization of basic_const_iterator (or derived from one).
basic_const_iterator's
operators, but there the recursion was independent of CWG 2369.
[2025-06-13; Reflector poll]
Set priority to 2 after reflector poll.
[2026-05-19; LWG telecon. Status changed: New → Ready.]
Something seems to be wrong or missing with CWG 2369 because it's silly to have to keep doing this in the library. We're changing a non-deduced parameter into a deduced one with a constraint that requries it to be the same type as it was anyway. In this particular case, P2600 might have helped.
Proposed resolution:
This wording is relative to N5001.
Modify 24.5.3.3 [const.iterators.iterator], class template basic_const_iterator synopsis, as indicated:
namespace std {
[…]
template<input_iterator Iterator>
class basic_const_iterator {
[…]
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator<(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator>(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator<=(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
template<not-a-const-iterator I, same_as<Iterator> J>
friend constexpr bool operator>=(const I& x, const basic_const_iterator<J>& y)
requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;
[…]
};
}
Modify 24.5.3.5 [const.iterators.ops] as indicated:
template<not-a-const-iterator I, same_as<Iterator> J> friend constexpr bool operator<(const I& x, const basic_const_iterator<J>& y) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<not-a-const-iterator I, same_as<Iterator> J> friend constexpr bool operator>(const I& x, const basic_const_iterator<J>& y) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<not-a-const-iterator I, same_as<Iterator> J> friend constexpr bool operator<=(const I& x, const basic_const_iterator<J>& y) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<not-a-const-iterator I, same_as<Iterator> J> friend constexpr bool operator>=(const I& x, const basic_const_iterator<J>& y) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;-23- Let
-24- Effects: Equivalent to:opbe the operator.return x op y.current_;
lazy_split_viewSection: 25.7.16.3 [range.lazy.split.outer] Status: Voting Submitter: Hewill Kang Opened: 2025-04-26 Last modified: 2026-05-22
Priority: 2
View other active issues in [range.lazy.split.outer].
View all other issues in [range.lazy.split.outer].
Discussion:
Consider (demo):
#include <print>
#include <ranges>
#include <sstream>
int main() {
std::istringstream is{"1 0 2 0 3"};
auto r = std::views::istream<int>(is)
| std::views::lazy_split(0)
| std::views::stride(2);
std::println("{}", r); // should print [[1], [3]]
}
The above leads to a hardened precondition failure in libstdc++, the reason is that we are iterating over the nested range as:
for (auto&& inner : r) {
for (auto&& elem : inner) {
// […]
}
}
which is disassembled as:
auto outer_it = r.begin();
std::default_sentinel_t out_end = r.end();
for(; outer_it != out_end; ++outer_it) {
auto&& inner_r = *outer_it;
auto inner_it = inner_r.begin();
std::default_sentinel_t inner_end = inner_r.end();
for(; inner_it != inner_end; ++inner_it) {
auto&& elem = *inner_it;
// […]
}
}
Since inner_it and output_it actually update the same iterator,
when we back to the outer loop, lazy_split_view::outer-iterator
is now equal to default_sentinel, which makes output_it reach the end,
so ++outer_it will increment the iterator past end, triggering the assertion.
Note that this also happens in MSVC-STL
when _ITERATOR_DEBUG_LEVEL is turned on.
It seems that extra flags are needed to fix this issue because output_it should not
be considered to reach the end when we back to the outer loop.
[2025-10-21; Reflector poll.]
Set priority to 2 after reflector poll.
"This is unfortunate. lazy_split is probably not very commonly used,
but handling input ranges was half the reason why we kept it around.
Can we reuse trailing_empty_ for this instead of adding a new flag?
Both flags have the same meaning, and the two cases where they are true
are disjoint: we need has_next_ when iterating through the inner range
exhausted the source range; we need trailing_empty_ when we find a delimiter
at the end of the source range when incrementing the outer iterator,
which by definition means that iterating through the inner range
didn't exhaust it."
This wording is relative to N5008.
Modify 25.7.16.3 [range.lazy.split.outer] as indicated:
[…]namespace std::ranges { template<input_range V, forward_range Pattern> requires view<V> && view<Pattern> && indirectly_comparable<iterator_t<V>, iterator_t<Pattern>, ranges::equal_to> && (forward_range<V> || tiny-range<Pattern>) template<bool Const> struct lazy_split_view<V, Pattern>::outer-iterator { private: using Parent = maybe-const<Const, lazy_split_view>; // exposition only using Base = maybe-const<Const, V>; // exposition only Parent* parent_ = nullptr; // exposition only iterator_t<Base> current_ = iterator_t<Base>(); // exposition only, present only // if V models forward_range bool trailing_empty_ = false; // exposition only bool has_next_ = false; // exposition only, present only // if forward_range<V> is false public: […] }; }constexpr explicit outer-iterator(Parent& parent) requires (!forward_range<Base>);[…]-2- Effects: Initializes
parent_withaddressof(parent)andhas_next_withcurrent != ranges::end(parent_->base_).constexpr outer-iterator& operator++();[…]-6- Effects: Equivalent to:
const auto end = ranges::end(parent_->base_); if (current == end) { trailing_empty_ = false; if constexpr (!forward_range<V>) has_next_ = false; return*this; } const auto [pbegin, pend] = subrange{parent_->pattern_}; if (pbegin == pend) ++current; else if constexpr (tiny-range<Pattern>) { current = ranges::find(std::move(current), end, *pbegin); if (current != end) { ++current; if (current == end) trailing_empty_ = true; } } else { do { auto [b, p] = ranges::mismatch(current, end, pbegin, pend); if (p == pend) { current = b; if (current == end) trailing_empty_ = true; break; // The pattern matched; skip it } } while (++current != end); } if constexpr (!forward_range<V>) if (current == end) has_next_ = false; return *this;friend constexpr bool operator==(const outer-iterator& x, default_sentinel_t);-8- Effects: Equivalent to:
if constexpr (!forward_range<V>) return !x.has_next_ && !x.trailing_empty_; else return x.current == ranges::end(x.parent_->base_) && !x.trailing_empty_;
[2025-10-21; Hewill Kang provides simpler wording]
[2026-05-22 LWG telecon; Status changed: New → Ready]
Proposed resolution:
This wording is relative to N5014.
Modify 25.7.16.3 [range.lazy.split.outer] as indicated:
constexpr outer-iterator& operator++();-6- Effects: Equivalent to:
const auto end = ranges::end(parent_->base_); if (current == end) { trailing_empty_ = false; return*this; } const auto [pbegin, pend] = subrange{parent_->pattern_}; if (pbegin == pend) ++current; else if constexpr (tiny-range<Pattern>) { current = ranges::find(std::move(current), end, *pbegin); if (current != end) { ++current; if (current == end) trailing_empty_ = true; else if constexpr (!forward_range<V>) trailing_empty_ = true; } } else { do { auto [b, p] = ranges::mismatch(current, end, pbegin, pend); if (p == pend) { current = b; if (current == end) trailing_empty_ = true; break; // The pattern matched; skip it } } while (++current != end); } return *this;
Section: 22.4.4.2 [tuple.cnstr] Status: Voting Submitter: Jiang An Opened: 2025-05-24 Last modified: 2026-05-22
Priority: 3
View other active issues in [tuple.cnstr].
View all other issues in [tuple.cnstr].
Discussion:
Per 20.2.8.2 [allocator.uses.construction]/1, uses-allocator construction is only defined for objects.
And presumably, an attempt to construct std::tuple of reference from an allocator_arg_t constructor
causes a hard error.
allocator_arg_t constructors are conditionally
deleted according to 22.4.4.2 [tuple.cnstr]/33. However, it's confusing that these constructors are
sometimes non-deleted when the tuple contains a reference, while there are hard errors in an instantiation
instead.
[2025-10-14; Reflector poll]
Set priority to 3 after reflector poll.
"There is a defect, but the right fix is to just define uses-allocator construction of references as equivalent to normal construction."
This wording is relative to N5008.
Modify 22.4.4.2 [tuple.cnstr] as indicated:
template<class Alloc> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a); […] template<class Alloc, tuple-like UTuple> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, UTuple&&);-32- Preconditions:
-33- Effects: Equivalent to the preceding constructors except that each element is constructed with uses-allocator construction (20.2.8.2 [allocator.uses.construction]). -?- Remarks: These constructors are defined as deleted ifAllocmeets the Cpp17Allocator requirements (16.4.4.6.1 [allocator.requirements.general]).is_reference_v<Ti>istruefor at least oneTi.
[2025-10-15; Jonathan provides new wording]
I don't think we care about uses-allocator construction of references in general.
You can't construct a reference using allocator_traits::construct nor put references in containers.
For a std::pair containing a reference, I think make_obj_using_allocator and uses_allocator_construction_args work fine.
The problem only exists in the std::tuple wording where we say that the elements are constructed using uses-allocator construction.
So let's just fix that.
[2026-05-22 LWG telecon; Status changed: New → Ready]
Proposed resolution:
This wording is relative to N5014.
Modify 22.4.4.2 [tuple.cnstr] as indicated:
template<class Alloc> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a); […] template<class Alloc, tuple-like UTuple> constexpr explicit(see below) tuple(allocator_arg_t, const Alloc& a, UTuple&&);-32- Preconditions:
-33- Effects: Equivalent to the preceding constructors except that each element of non-reference type is constructed with uses-allocator construction (20.2.8.2 [allocator.uses.construction]).Allocmeets the Cpp17Allocator requirements (16.4.4.6.1 [allocator.requirements.general]).
as_awaitable(expr, p) does not define semantics of call if p is not an lvalueSection: 33.13.1 [exec.as.awaitable] Status: Voting Submitter: Lewis Baker Opened: 2025-08-27 Last modified: 2026-05-19
Priority: 2
View other active issues in [exec.as.awaitable].
View all other issues in [exec.as.awaitable].
Discussion:
The wording in 33.13.1 [exec.as.awaitable] p7 defines the semantics of a call to as_awaitable(expr, p)
where p is an lvalue. However, it does not specify what the behaviour is if p is not an lvalue.
as_awaitable(expr, p) is ill-formed if p is not an lvalue.
[2026-01-16; Reflector poll.]
Set priority to 2 after reflector poll.
This wording is relative to N5014.
Modify 33.13.1 [exec.as.awaitable] as indicated:
-7-
as_awaitableis a customization point object. For subexpressionsexprandpwherepis an lvalue,Exprnames the typedecltype((expr))andPromisenames the typedecay_t<decltype((p))>, ifpis not an lvalue,as_awaitable(expr, p)is ill-formed, otherwiseas_awaitable(expr, p)is expression-equivalent to, except that the evaluations ofexprandpare indeterminately sequenced:
[2026-01-16; Jonathan provides updated wording]
[2026-05-19; LWG telecon. Status changed: New → Ready.]
Proposed resolution:
This wording is relative to N5032.
Modify 33.13.1 [exec.as.awaitable] as indicated:
-7-
as_awaitableis a customization point object. For subexpressionsexprandpwherepis an lvalue,Exprnames the typedecltype((expr))andPromisenames the typedecay_t<decltype((p))>,as_awaitable(expr, p)is ill-formed ifpis not an lvalue, otherwiseas_awaitable(expr, p)is expression-equivalent to the expression specified below, except that the evaluations ofexprandpare indeterminately sequenced:
<simd> doesn't provide std::begin/end
Section: 24.7 [iterator.range] Status: Voting Submitter: Hewill Kang Opened: 2025-09-26 Last modified: 2026-05-22
Priority: 3
View other active issues in [iterator.range].
View all other issues in [iterator.range].
Discussion:
std::simd::basic_vec and std::simd::basic_mask are ranges since P3480R6,
it is reasonable to enable range access utilities when introducing <simd>.
[2025-10-17; Reflector poll.]
Set priority to 3 after reflector poll.
"This will be the first time we add the free-function begin/end interface to support a type where end returns a sentinel.
I would not want to set precedent here without at least taking the time to think about it, and acknowledge a deliberate intent."
"The intent has been deliberate since P0184R0."
[2026-05-22 LWG telecon; Status changed: New → Ready]
Proposed resolution:
This wording is relative to N5014.
Modify 24.7 [iterator.range] as indicated:
-1- In addition to being available via inclusion of the
<iterator>header, the function templates in 24.7 [iterator.range] are available when any of the following headers are included:<array>,<deque>,<flat_map>,<flat_set>,<forward_list>,<hive>,<inplace_vector>,<list>,<map>,<regex>,<set>,<simd>,<span>,<string>,<string_view>,<unordered_map>,<unordered_set>, and<vector>.
get_env noexcept-ness from inline_schedulerSection: 33.13.4 [exec.inline.scheduler] Status: Voting Submitter: Eric Niebler Opened: 2025-11-05 Last modified: 2026-05-19
Priority: 4
Discussion:
33.13.4 [exec.inline.scheduler] bullet (3.2) reads:
- (3.2) — the expression
get_completion_scheduler<set_value_t>(get_env(sndr))has typeinline_schedulerand is potentially-throwing if and only ifget_env(sndr)is potentially-throwing.
get_env(sndr) is never potentially-throwing because it mandates that sndr.get_env() cannot throw,
see 33.5.5 [exec.get.env].
get_completion_scheduler<set_*_t>(attrs) mandates that
attrs.query(get_completion_scheduler<set_*_t>) cannot throw.
[2026-01-16; Reflector poll.]
Set priority to 4 after reflector poll.
"33.12.1.2 [exec.run.loop.types] (7.2) has the same redundancy."
This wording is relative to N5014.
Modify 33.13.4 [exec.inline.scheduler] as indicated:
-3- Let
sndrbe an expression of typeinline-sender, letrcvrbe an expression such thatreceiver_of<decltype((rcvr)), CS>istruewhereCSiscompletion_signatures<set_value_t()>, then:
- (3.1) — the expression
connect(sndr, rcvr)has typeinline-state<remove_cvref_t<decltype((rcvr))>>and is potentially-throwing if and only if((void)sndr, auto(rcvr))is potentially-throwing, and- (3.2) — the expression
get_completion_scheduler<set_value_t>(get_env(sndr))has typeinline_schedulerand is potentially-throwing if and only if.get_env(sndr)is potentially-throwing
[2026-01-16; Jonathan provides updated wording]
LWG believes the original issue discussion is wrong to say that
get_env(sndr) can never throw, because the expression sndr itself
could throw.
However, we already state that get_env and get_completion_scheduler
do not throw, so it's redundant to say that
get_completion_scheduler(get_env(sndr)) only throws if sndr throws.
If nothing except sndr in that expression is allowed to throw,
then it's just stating the obvious.
This wording is relative to N5032.
Modify 33.13.4 [exec.inline.scheduler] as indicated:
-3- Let
sndrbe an expression of typeinline-sender, letrcvrbe an expression such thatreceiver_of<decltype((rcvr)), CS>istruewhereCSiscompletion_signatures<set_value_t()>, then:
- (3.1) — the expression
connect(sndr, rcvr)has typeinline-state<remove_cvref_t<decltype((rcvr))>>and is potentially-throwing if and only if((void)sndr, auto(rcvr))is potentially-throwing, and- (3.2) — the expression
get_completion_scheduler<set_value_t>(get_env(sndr))has typeinline_schedulerand is potentially-throwing if and only if.get_env(sndr)is potentially-throwing
Modify 33.12.1.2 [exec.run.loop.types] as indicated:
-7- Let
sndrbe an expression of typerun-loop-sender, letrcvrbe an expression such thatreceiver_of<decltype((rcvr)), CS>istruewhereCSis thecompletion_signaturesspecialization above. LetCbe eitherset_value_torset_stopped_t. Then:
- (7.1) — The expression
connect(sndr, rcvr)has typerun-loop-opstate<decay_t<decltype((rcvr))>>and is potentially-throwing if and only if(void(sndr), auto(rcvr))is potentially-throwing.- (7.2) — The expression
get_completion_scheduler<C>(get_env(sndr))is potentially-throwing if and only ifhas typesndris potentially-throwing,run-loop-scheduler,and compares equal to therun-loop-schedulerinstance from whichsndrwas obtained.
[2026-05-15; Tim rebases wording to new working draft]
The inline_scheduler change is no longer needed.
[2026-05-19; LWG telecon. Status changed: New → Ready.]
Proposed resolution:
This wording is relative to N5046.
Modify 33.12.1.2 [exec.run.loop.types] as indicated:
-8- Let
sndrbe an expression of typerun-loop-sender, letrcvrbe an expression such thatreceiver-of<decltype((rcvr)), CS>istruewhereCSis thecompletion_signaturesspecialization above. LetCbe eitherset_value_torset_stopped_t. Then:
- (8.1) — The expression
connect(sndr, rcvr)has typerun-loop-opstate<decay_t<decltype((rcvr))>>and is potentially-throwing if and only if(void(sndr), auto(rcvr))is potentially-throwing.- (8.2) — The expression
get_completion_scheduler<C>(get_env(sndr))is potentially-throwing if and only ifhas typesndris potentially-throwing,run-loop-scheduler,and compares equal to therun-loop-schedulerinstance from whichsndrwas obtained.
is_steady of a Cpp17Clock type required to be usable in constant expressions?Section: 30.3 [time.clock.req] Status: Voting Submitter: Jiang An Opened: 2025-11-26 Last modified: 2026-05-22
Priority: 3
View all other issues in [time.clock.req].
Discussion:
In 30.3 [time.clock.req]/[tab:time.clock], the static data member C1::is_steady seemingly
indicates the property of the clock statically, as the requirements say "always".
C1::steady should be usable in a constant expression. A hostile
reading may indicate that it is allowed to make C1::steady only defined in some separated translated
unit and/or initialized from some non-constant expression.
Also, it is not very clear that the "always" means the conditions always hold in all executions,
or only always hold in the current execution. The latter reading allows C1::steady to vary between
executions.
[2026-01-16; Reflector poll.]
Set priority to 3 after reflector poll.
Unanimous preference for Option A.
This wording is relative to N5032.
[Drafting Note: Two mutually exclusive options are prepared, depicted below by Option A and Option B, respectively.]
Option A: Requiring usability in constant expressions.
Modify Table [tab:time.clock] — Cpp17Clock requirements as indicated:
Table 131 — Cpp17Clock requirements [tab:time.clock] Expression Return type Operational semantics […]C1::is_steadyconst booltrueift1 <= t2is alwaystrueand the time between
clock ticks is constant,
otherwisefalse.
C1::is_steadyshall be usable in constant expressions (7.7 [expr.const]).[…]
Option B: Allowing varying between executions.
Modify Table [tab:time.clock] — Cpp17Clock requirements as indicated:
Table 131 — Cpp17Clock requirements [tab:time.clock] Expression Return type Operational semantics […]C1::is_steadyconst booltrueift1 <= t2is alwaystrueand the time between
clock ticks is constant
for the duration of the current program execution,
otherwisefalse.
[Note ?:C1::is_steadyis not required to be usable in constant expressions (7.7 [expr.const]) and can vary between different executions. — end note][…]
[2026-05-22; Tim updates wording to use Option A based on reflector discussion]
[2026-05-22 LWG telecon; Status changed: New → Ready]
Proposed resolution:
This wording is relative to N5032.
Modify Table [tab:time.clock] — Cpp17Clock requirements as indicated:
Table 131 — Cpp17Clock requirements [tab:time.clock] Expression Return type Operational semantics […]C1::is_steadyconst booltrueift1 <= t2is alwaystrueand the time between
clock ticks is constant,
otherwisefalse.
C1::is_steadyshall be usable in constant expressions (7.7 [expr.const]).[…]
std::ranges::size in ranges algorithmsSection: 26.2 [algorithms.requirements] Status: Voting Submitter: Ruslan Arutyunyan Opened: 2025-12-12 Last modified: 2026-05-19
Priority: 3
View other active issues in [algorithms.requirements].
View all other issues in [algorithms.requirements].
Discussion:
26.2 [algorithms.requirements] paragraph 14 says that the algorithms in ranges namespace are implemented
as if they are dispatched to the corresponding overload with iterator and sentinel. However, there are two problems
with the current wording:
We only allow to call either std::ranges::end(r) or std::ranges::next(std::ranges::begin(r), std::ranges::end())
to calculate a corresponding sentinel. However, this is a pessimization for some ranges because we can have sized
ranges without sized_sentinel_for. Consider the following example:
const char str[] = "something";
ranges::subrange<const char*,
null_sentinel_t,
subrange_kind::sized> sr(ranges::begin(str),
null_sentinel,
ranges::size(str) - 1);
my::ranges::next(sr.begin(), sr.end()); // this line serially calculates iterator that is equal to sr.end()
Despite the fact that we know the size of the range and that the range is the random access one,
std::ranges::next calculates the iterator serially, because it only has constant time complexity
optimization for sized_sentinel_for. We could clearly achieve better complexity with a different
API or with the different overload of the same API.
It is unclear when an algorithm calls std::ranges::end(r) and when it calls
std::ranges::next(std::ranges::begin(r), std::ranges::end()) to calculate the
corresponding sentinel because there is no established priority between them.
Maybe, it's smaller problem but still it's worth clarifying in my opinion.
[2026-01-16; Reflector poll.]
Set priority to 3 after reflector poll.
"Should be 'the type of r models sized_range"
"It's a lot of words, could be shorter. Would prefer not to establish priority, but let implementation decide if/when to use ranges::next."
"Would counted_iterator(ranges::begin(r), ranges::distance(r)) and default_sentinel be better?"
This wording is relative to N5032.
Modify 26.2 [algorithms.requirements] as indicated:
[…]
-14- Overloads of algorithms that takerangearguments (25.4.2 [range.range]) behave as if they are implemented by dispatching to the overload in namespacerangesthat takes separate iterator and sentinel arguments, where for each range argumentr
(14.1) — a corresponding iterator argument is initialized with
ranges::begin(r)and(14.2) — a corresponding sentinel argument is initialized with
one of the following:ranges::end(r), orranges::next(ranges::begin(r), ranges::end(r))if the type ofrmodelsforward_rangeand computingranges::nextmeets the specified complexity requirements.
(14.2.1) — if the type of
rmodelsforward_rangeand computingranges::nextmeets the specified complexity requirements then
(14.2.1.1) —
ranges::next(ranges::begin(r), ranges::size(r))ifrmodelssized_range, otherwise(14.2.1.2) —
ranges::next(ranges::begin(r), ranges::end(r)).(14.2.2) — Otherwise,
std::ranges::end(s).
[2026-01-30; Jonathan provides new wording]
We can just say that the value is either the end iterator, or begin+N,
without saying when or how the implementation obtains begin+N.
It might use ranges::next(ranges::begin(r), std::ranges::end(r)) or
ranges::next(ranges::begin(r), ranges::distance(r)) or something else,
only the value matters not how it's obtained.
Paragraph 11 in the same subclause defines what begin+N means if the
iterator is not a random access iterator.
[2026-01-30; LWG telecon. Status → Open.]
[2026-05-19; LWG telecon. Status changed: Open → Ready.]
Proposed resolution:
This wording is relative to N5032.
Modify 26.2 [algorithms.requirements] as indicated:
[…]
-14- Overloads of algorithms that takerangearguments (25.4.2 [range.range]) behave as if they are implemented by dispatching to the overload in namespacerangesthat takes separate iterator and sentinel arguments, where for each range argumentr
(14.1) — a corresponding iterator argument is initialized with
ranges::begin(r)and(14.2) — a corresponding sentinel argument is initialized with
ranges::end(r), orranges::next(ranges::begin(r), ranges::end(r))if the type ofrmodelsforward_rangeand computingranges::nextmeets the specified complexity requirements.ranges::begin(r) + NwhereNis equal toranges::distance(r).
Section: 32.5.4 [atomics.order] Status: Voting Submitter: jim x Opened: 2026-02-10 Last modified: 2026-05-19
Priority: 4
View other active issues in [atomics.order].
View all other issues in [atomics.order].
Discussion:
32.5.4 [atomics.order] p10 says:
Atomic read-modify-write operations shall always read the last value (in the modification order) written before the write associated with the read-modify-write operation.
We do not write side effects to the modification order, instead we write side effects to an atomic object. These modifications occur in a single total order, called the modification order. That is, the modification order describes how these operations are ordered.
Moreover, we called the compared element in the modification order "modification" or "side effect", as shown in 6.10.2.2 [intro.races]. In addition, we don't use "a side effectX
written before Y in the modification order"; we use "X precedes/follows
Y in the modification order" to describe the relationship of order between two modifications.
Suggested Resolution:
To make the wording more formal and have a consistent way with 6.10.2.2 [intro.races], the proposed wording is:
An atomic read-modify-write operation shall always read the value of a side effect
X, whereXis the last side effect that precedes the side effect of the read-modify-write operation in the modification order.
Another option is:
An atomic read-modify-write operation shall always read the value of a side effect that immediately precedes the side effect of the read-modify-write operation in the modification order.
[2026-02-27; Reflector poll.]
Set priority to 4 after reflector poll.
The existing wording was considered to be correct. Support for option B as wording improvement was expressed, as making the wording more formal and "de-shalling".
[2026-03-23: Croydon, SG1]
Poll: Approve option B to resolve LWG 4521 and send it back to LWG
Outcome: No objection to unanimous consentThis wording is relative to N5032.
[Drafting Note: Two mutually exclusive options are prepared, depicted below by Option A and Option B, respectively.
As a drive-by fix, in both options we also replace the "shall" wording by an indicative voice, because 32.5.4 [atomics.order] p10 already imposes a requirement on the implementation/language. ]
Option A:
Modify 32.5.4 [atomics.order] as indicated:
-10- An atomic read-modify-write operation
s shallalways reads thelastvalue from the side effectX, whereXis the last side effect that precedes the side effect of the read-modify-write operation(in the modification order) written before the write associated with the read-modify-write operation.
Option B:
Modify 32.5.4 [atomics.order] as indicated:
-10- An atomic read-modify-write operation
s shallalways reads thelastvalue from the side effect that immediately precedes the side effect of the read-modify-write operation(in the modification order) written before the write associated with the read-modify-write operation.
[2026-05-19; LWG telecon. Status changed: New → Ready.]
Proposed resolution:
This wording is relative to N5032.
[Drafting Note: As a drive-by fix, we also replace the "shall" wording by an indicative voice, because 32.5.4 [atomics.order] p10 already imposes a requirement on the implementation/language. ]
Modify 32.5.4 [atomics.order] as indicated:
-10- An atomic read-modify-write operation
s shallalways reads thelastvalue from the side effect that immediately precedes the side effect of the read-modify-write operation(in the modification order) written before the write associated with the read-modify-write operation.
ranges::to constructs associative containers via c.emplace(c.end(), *it)Section: 25.5.7.1 [range.utility.conv.general] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2024-07-16 Last modified: 2026-05-29
Priority: 2
Discussion:
When ranges::to constructs an associative container, if there is no range version constructor
for C and the input range is not common range, ranges::to will dispatch to the
bullet 25.5.7.2 [range.utility.conv.to] (2.1.4), which first default-constructs the container
and emplaces the element through c.emplace(c.end(), *it).
emplace() on an associative container as it does
not expect an iterator as the first argument, and since map::emplace(), for instance, is not
constrained, which turns out a hard error because we are trying to make a pair with
{it, pair}.
Given that libstdc++ currently does not implement the range constructor for associative containers, the following
illustrates the issue:
#include <ranges>
#include <set>
auto s = std::views::iota(0)
| std::views::take(5)
| std::ranges::to<std::set>(); // hard error
The proposed resolution simply removes the emplace() branch. Although this means that we always use
insert() to fill associative containers, such an impact seems negligible.
[2024-07-23; This was caused by LWG 4016(i).]
[2024-08-02; Reflector poll]
Set priority to 2 after reflector poll.
"Would like to preserve the ability to use emplace. Tim suggested trying
emplace_hint first, then emplace."
"I tried it, it gets very verbose, because we might also want to try
insert(*it) instead of insert(c.end(), *it) if emplace(*it) is not valid
for associative containers, because c.end() might not be a good hint."
"It might be suboptimal, but it still works."
This wording is relative to N4986.
Modify 25.5.7.1 [range.utility.conv.general] as indicated:
-4- Let
container-appendablebe defined as follows:template<class Container, class Ref> constexpr bool container-appendable = // exposition only requires(Container& c, Ref&& ref) { requires (requires { c.emplace_back(std::forward<Ref>(ref)); } || requires { c.push_back(std::forward<Ref>(ref)); } ||requires { c.emplace(c.end(), std::forward<Ref>(ref)); } ||requires { c.insert(c.end(), std::forward<Ref>(ref)); }); };-5- Let
container-appendbe defined as follows:template<class Container> constexpr auto container-append(Container& c) { // exposition only return [&c]<class Ref>(Ref&& ref) { if constexpr (requires { c.emplace_back(declval<Ref>()); }) c.emplace_back(std::forward<Ref>(ref)); else if constexpr (requires { c.push_back(declval<Ref>()); }) c.push_back(std::forward<Ref>(ref));else if constexpr (requires { c.emplace(c.end(), declval<Ref>()); }) c.emplace(c.end(), std::forward<Ref>(ref));else c.insert(c.end(), std::forward<Ref>(ref)); }; };
[2025-03-13; Jonathan provides improved wording for Tim's suggestion]
It's true that for some cases it might be optimal to use c.emplace(ref)
instead of c.emplace_hint(c.end(), ref) but I don't think I care.
A bad hint is not expensive, it's just an extra comparison then the hint
is ignored.
And this code path isn't going to be used for std::set or std::map,
only for user-defined associative containers that don't have a from_range_t
constructor or a C(Iter,Sent) constructor.
I think just fixing the original issue is all we need,
rather than trying to handle every possible way to insert elements.
This is a simpler, portable reproducer that doesn't depend on the current
implementation status of std::set in libstdc++:
#include <ranges> #include <set> struct Set : std::set<int> { Set() { }; // No other constructors }; int main() { int a[1]; auto okay = std::ranges::to<std::set<int>>(a); auto ohno = std::ranges::to<Set>(a); }
This wording is relative to N5001.
Modify 25.5.7.1 [range.utility.conv.general] as indicated:
-4- Let
container-appendablebe defined as follows:template<class Container, class Ref> constexpr bool container-appendable = // exposition only requires(Container& c, Ref&& ref) { requires (requires { c.emplace_back(std::forward<Ref>(ref)); } || requires { c.push_back(std::forward<Ref>(ref)); } || requires { c.emplace_hint(c.end(), std::forward<Ref>(ref)); } || requires { c.emplace(c.end(), std::forward<Ref>(ref)); } || requires { c.insert(c.end(), std::forward<Ref>(ref)); }); };-5- Let
container-appendbe defined as follows:template<class Container> constexpr auto container-append(Container& c) { // exposition only return [&c]<class Ref>(Ref&& ref) { if constexpr (requires { c.emplace_back(declval<Ref>()); }) c.emplace_back(std::forward<Ref>(ref)); else if constexpr (requires { c.push_back(declval<Ref>()); }) c.push_back(std::forward<Ref>(ref)); else if constexpr (requires { c.emplace_hint(c.end(), declval<Ref>()); }) c.emplace_hint(c.end(), std::forward<Ref>(ref)); else if constexpr (requires { c.emplace(c.end(), declval<Ref>()); }) c.emplace(c.end(), std::forward<Ref>(ref)); else c.insert(c.end(), std::forward<Ref>(ref)); }; };
[2026-05-22; Jonathan provides new wording]
Hewill noted that all the standard sequence containers that could use
c.emplace(c.end(), ref) would already have used the emplace_back branch,
so there's no point using c.emplace. Just change that branch to use
emplace_hint for associative containers.
[2026-05-29; Reflector poll.]
Set status to Tentatively Ready after nine votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5046.
Modify 25.5.7.1 [range.utility.conv.general] as indicated:
-4- Let
container-appendablebe defined as follows:template<class Container, class Ref> constexpr bool container-appendable = // exposition only requires(Container& c, Ref&& ref) { requires (requires { c.emplace_back(std::forward<Ref>(ref)); } || requires { c.push_back(std::forward<Ref>(ref)); } || requires { c.emplace_hint(c.end(), std::forward<Ref>(ref)); } || requires { c.insert(c.end(), std::forward<Ref>(ref)); }); };-5- Let
container-appendbe defined as follows:template<class Container> constexpr auto container-append(Container& c) { // exposition only return [&c]<class Ref>(Ref&& ref) { if constexpr (requires { c.emplace_back(declval<Ref>()); }) c.emplace_back(std::forward<Ref>(ref)); else if constexpr (requires { c.push_back(declval<Ref>()); }) c.push_back(std::forward<Ref>(ref)); else if constexpr (requires { c.emplace_hint(c.end(), declval<Ref>()); }) c.emplace_hint(c.end(), std::forward<Ref>(ref)); else c.insert(c.end(), std::forward<Ref>(ref)); }; };
std::to_(w)string?Section: 17.3.2 [version.syn] Status: Tentatively Ready Submitter: Jiang An Opened: 2026-02-25 Last modified: 2026-05-22
Priority: 2
View other active issues in [version.syn].
View all other issues in [version.syn].
Discussion:
P3391R2 added constexpr to integral overloads of std::to_string
and std::to_wstring. However, the paper only added one feature-test macro
__cpp_lib_constexpr_format, which doesn't seem clearly related to std::to_(w)string.
__cpp_lib_to_string, perhaps we should
bump that FTM. It might be also reasonable to bump __cpp_lib_constexpr_string for pure
constexpr additions.
[2026-04-13; Reflector poll.]
Set priority to 2 after reflector poll.
This wording is relative to N5032.
[Drafting Note: Two mutually exclusive options are prepared, depicted below by Option A and Option B, respectively.]
Option A:
Modify 17.3.2 [version.syn] as indicated:
[…] #define __cpp_lib_to_string202306L202511L // also in <string> […]
Option B:
Modify 17.3.2 [version.syn] as indicated:
[…] #define __cpp_lib_constexpr_string201907L202511L // also in <string> […]
[2026-05-11; Select single resolution.]
Update wording to option B, that had slight preference in reflector discussion.
[2026-05-22; Reflector poll.]
Set status to Tentatively Ready after six votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5032.
Modify 17.3.2 [version.syn] as indicated:
[…] #define __cpp_lib_constexpr_string201907L202511L // also in <string> […]
apply_result, is_applicableSection: 17.3.2 [version.syn] Status: Tentatively Ready Submitter: Tomasz Kamiński Opened: 2026-04-08 Last modified: 2026-05-30
Priority: Not Prioritized
View other active issues in [version.syn].
View all other issues in [version.syn].
Discussion:
The paper P1317R2 "Remove return type deduction in std::apply", which changed
the definition of std::apply and introduced the new traits is_applicable,
is_nothrow_applicable, and apply_result, was accepted in Sofia.
__cpp_lib_reflection.
This leads to situation, when an implementation that does not yet implement P1317R2,
and thus does not define the corresponding meta functions, should not define __cpp_lib_reflection.
I propose to assign the following __cpp_lib_apply macro values to account to above papers:
202506L for P1317R2 also in <tuple>, <type_traits>
202603L for the corresponding parts of P3795R2 also in <meta>
The proposed resolution for the standard working paper represents only the effective end state.
[2026-05-29; Reflector poll.]
Set status to Tentatively Ready after six votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5032.
Modify 17.3.2 [version.syn] as indicated:
#define __cpp_lib_apply202506L202603L // freestanding, also in <tuple>, <type_traits>, <meta>
std::execution::spawn_future is mishandling dependent sendersSection: 33.9.12.18 [exec.spawn.future] Status: Tentatively Ready Submitter: Eric Niebler Opened: 2026-04-11 Last modified: 2026-05-30
Priority: Not Prioritized
View all other issues in [exec.spawn.future].
Discussion:
In 33.9.12.18 [exec.spawn.future] p7, we see this:
-7- Let
spawn-future-statebe the exposition-only class template:namespace std::execution { template<class Alloc, scope_token Token, sender Sender, class Env> struct spawn-future-state // exposition only : spawn-future-state-base<completion_signatures_of_t<future-spawned-sender<Sender, Env>>> { using sigs-t = // exposition only completion_signatures_of_t<future-spawned-sender<Sender, Env>>; using receiver-t = // exposition only spawn-future-receiver<sigs-t>; using op-t = // exposition only connect_result_t<future-spawned-sender<Sender, Env>, receiver-t>; […] }; […] }
where spawn-future-sender<Sender, Env> is the type of
write_env(stop-when(declval<Sender>(), declval<stoken-t>()), declval<Env>()).
sigs-t. There is a difference between asking for
a sender's completion signatures with an empty environment, and asking with no environment.
In sigs-t, we are asking for the completion signatures with no environment. That is
asking the sender for its non-dependent completions. But if Sender is a dependent sender,
then so is future-spawned-sender<Sender, Env>, and so this line will not compile.
A dependent sender cannot provide non-dependent completion signatures.
The spawn_future algorithm connects the future-spawned-sender with a receiver that
has an empty environment. Therefore, we should be using the empty environment when computing the
future-spawned-sender's completion signatures.
[2026-05-29; Reflector poll.]
Set status to Tentatively Ready after five votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5032.
Modify 33.9.12.18 [exec.spawn.future] as indicated:
-7- Let
spawn-future-statebe the exposition-only class template:namespace std::execution { template<class Alloc, scope_token Token, sender Sender, class Env> struct spawn-future-state // exposition only : spawn-future-state-base<completion_signatures_of_t<future-spawned-sender<Sender, Env>, env<>>> { using sigs-t = // exposition only completion_signatures_of_t<future-spawned-sender<Sender, Env>, env<>>; using receiver-t = // exposition only spawn-future-receiver<sigs-t>; using op-t = // exposition only connect_result_t<future-spawned-sender<Sender, Env>, receiver-t>; […] }; […] }
make-state<Rcvr>::state-type::complete uses Env which is not in scopeSection: 33.9.12.12 [exec.when.all] Status: Tentatively Ready Submitter: Abhinav Agarwal Opened: 2026-05-12 Last modified: 2026-05-30
Priority: Not Prioritized
View other active issues in [exec.when.all].
View all other issues in [exec.when.all].
Discussion:
In 33.9.12.12 [exec.when.all], the specification of
make-state::state-type::complete
(paragraph 15, bullet (15.3)) defines sends-stopped
as true if and only if there exists an element S of Sndrs such that
completion_signatures_of_t<S, when-all-env<Env>>
contains set_stopped_t().
Env is in scope at this point.
state-type is specified in the context of
make-state<Rcvr>::operator()<Sndrs...>;
the relevant named template parameters there are Rcvr and
Sndrs.... Env is a template parameter of
check-types<Sndr, Env...>, which is a separate
member function whose scope does not extend into make-state.
Other environment-dependent wording within the make-state
scope uses env_of_t<Rcvr>:
The stop_callback alias (paragraph 12 within the make-state
class template synopsis) uses
stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, on-stop-request>
The values_tuple alias (paragraph 13) uses
value_types_of_t<Sndrs, FWD-ENV-T(env_of_t<Rcvr>), […]>
The sends-stopped definition in bullet (15.3) is the only use
of bare Env in this scope.
[2026-05-29; Reflector poll.]
Set status to Tentatively Ready after five votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N5046.
Modify 33.9.12.12 [exec.when.all] as indicated:
-15- The member
void state-type::complete(Rcvr& rcvr) noexceptbehaves as follows:
(15.1) — If
dispis equal todisposition::started, evaluates: […](15.2) — Otherwise, if
dispis equal todisposition::error, evaluates: […](15.3) — Otherwise, evaluates:
if constexpr (sends-stopped) { on_stop.reset(); set_stopped(std::move(rcvr)); }where
sends-stoppedequals true if and only if there exists an elementSofSndrssuch thatcompletion_signatures_of_t<S, when-all-env<containsEnvenv_of_t<Rcvr>>>set_stopped_t().