C++ Standard Library Ready Issues to be moved in Brno, Jun. 2026

Doc. no. P4258R0
Date:

2026-06-08

Audience: WG21
Reply to: Jonathan Wakely <lwgchair@gmail.com>

Ready Issues


3417. Missing volatile atomic deprecations

Section: 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.

First, 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.

  1. 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 volatile overload of this function, is_always_lock_free is true.

    -29- Preconditions: […]

    […]

    void notify_one() volatile noexcept;
    void notify_one() noexcept;
    

    Constraints: For the volatile overload of this function, is_always_lock_free is true.

    -32- Effects: […]

    […]

    void notify_all() volatile noexcept;
    void notify_all() noexcept;
    

    Constraints: For the volatile overload of this function, is_always_lock_free is true.

    -34- Effects: […]

    […]

  2. 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_free is false:

    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;
    

4125. move_iterator's default constructor should be constrained

Section: 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>.

Although this is not current standard-conforming behavior, such constraint does make sense since 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.

  1. 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
      };
    }
    
  2. Modify 24.5.4.4 [move.iter.cons] as indicated:

    constexpr move_iterator();
    

    -1- Effects: Value-initializes current.


4158. packaged_task::operator= should abandon its shared state

Section: 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)), where f is 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.

Previous resolution [SUPERSEDED]

This wording is relative to N4988.

  1. Modify 32.10.10.2 [futures.task.members] as indicated:

    packaged_task& operator=(packaged_task&& rhs) noexcept;
    

    -11- Effects:

    1. (11.1) — Releases Abandons any shared state (32.10.5 [futures.state]);
    2. (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.

  1. 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 if Equivalent to promise(std::move(rhs)).swap(*this).

    -10- Returns: *this.

  2. Modify 32.10.10.2 [futures.task.members] as indicated:

    packaged_task& operator=(packaged_task&& rhs) noexcept;
    

    -11- Effects:

    1. (11.1) — Releases any shared state (32.10.5 [futures.state]);
    2. (11.2) — calls Equivalent to packaged_task(std::move(rhs)).swap(*this).

    -?- Returns: *this.


4182. Definition of NULL is too broad

Section: 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.".

17.2.3 [support.types.nullptr]/2 reads: "The macro 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."

Previous resolution [SUPERSEDED]

This wording is relative to N5001.

  1. Modify 17.2.3 [support.types.nullptr] as indicated:

    -2- The macro NULL is an implementation-defined null pointer constant that is a literal (5.13.2 [lex.icon], 5.13.8 [lex.nullptr]).footnote 161

    161) Possible definitions include 0 and 0L, 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.

  1. Modify 17.2.3 [support.types.nullptr] as indicated:

    -2- The macro NULL is an implementation-defined null pointer constant that is a literal (5.13.2 [lex.icon], 5.13.8 [lex.nullptr]).footnote 161

    161) Possible definitions include nullptr, 0 and 0L, but not (void*)0.


4218. Constraint recursion in basic_const_iterator's relational operators due to ADL + CWG 2369

Section: 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.

The constraint recursion is diagnosed by GCC (See godbolt demo). Other compilers accept the example because they don't implement CWG 2369, as far as I know.

GCC trunk works around this issue by giving these friend relational operators a dependent second operand of the form 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).

LWG 3769(i) is an earlier issue about constraint recursion in 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.

  1. 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>;
        […]
      };
    }
    
  2. 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 op be the operator.

    -24- Effects: Equivalent to: return x op y.current_;


4249. The past end issue for lazy_split_view

Section: 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."

Previous resolution [SUPERSEDED]

This wording is relative to N5008.

  1. 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_ with addressof(parent) and has_next_ with current != 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.

  1. 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;
    

4267. Uses-allocator construction is meaningless for tuple of references

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.

Since C++23/P2255R2, it seems that these 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."

Previous resolution [SUPERSEDED]

This wording is relative to N5008.

  1. 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: Alloc meets the Cpp17Allocator requirements (16.4.4.6.1 [allocator.requirements.general]).

    -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 if is_reference_v<Ti> is true for at least one Ti.

[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.

  1. 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: Alloc meets the Cpp17Allocator requirements (16.4.4.6.1 [allocator.requirements.general]).

    -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]).


4359. as_awaitable(expr, p) does not define semantics of call if p is not an lvalue

Section: 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.

We should probably say that 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.

Previous resolution [SUPERSEDED]

This wording is relative to N5014.

  1. Modify 33.13.1 [exec.as.awaitable] as indicated:

    -7- as_awaitable is a customization point object. For subexpressions expr and p where p is an lvalue, Expr names the type decltype((expr)) and Promise names the type decay_t<decltype((p))>, if p is not an lvalue, as_awaitable(expr, p) is ill-formed, otherwise as_awaitable(expr, p) is expression-equivalent to, except that the evaluations of expr and p are 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.

  1. Modify 33.13.1 [exec.as.awaitable] as indicated:

    -7- as_awaitable is a customization point object. For subexpressions expr and p where p is an lvalue, Expr names the type decltype((expr)) and Promise names the type decay_t<decltype((p))>, as_awaitable(expr, p) is ill-formed if p is not an lvalue, otherwise as_awaitable(expr, p) is expression-equivalent to the expression specified below, except that the evaluations of expr and p are indeterminately sequenced:


4385. Including <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.

  1. 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>.


4471. Remove test for get_env noexcept-ness from inline_scheduler

Section: 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:

get_env(sndr) is never potentially-throwing because it mandates that sndr.get_env() cannot throw, see 33.5.5 [exec.get.env].

also, 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."

Previous resolution [SUPERSEDED]

This wording is relative to N5014.

  1. Modify 33.13.4 [exec.inline.scheduler] as indicated:

    -3- Let sndr be an expression of type inline-sender, let rcvr be an expression such that receiver_of<decltype((rcvr)), CS> is true where CS is completion_signatures<set_value_t()>, then:

    • (3.1) — the expression connect(sndr, rcvr) has type inline-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 type inline_scheduler and 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.

Previous resolution [SUPERSEDED]

This wording is relative to N5032.

  1. Modify 33.13.4 [exec.inline.scheduler] as indicated:

    -3- Let sndr be an expression of type inline-sender, let rcvr be an expression such that receiver_of<decltype((rcvr)), CS> is true where CS is completion_signatures<set_value_t()>, then:

    • (3.1) — the expression connect(sndr, rcvr) has type inline-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 type inline_scheduler and is potentially-throwing if and only if get_env(sndr) is potentially-throwing.
  2. Modify 33.12.1.2 [exec.run.loop.types] as indicated:

    -7- Let sndr be an expression of type run-loop-sender, let rcvr be an expression such that receiver_of<decltype((rcvr)), CS> is true where CS is the completion_signatures specialization above. Let C be either set_value_t or set_stopped_t. Then:

    • (7.1) — The expression connect(sndr, rcvr) has type run-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 if sndr is potentially-throwing, has type run-loop-scheduler, and compares equal to the run-loop-scheduler instance from which sndr was 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.

  1. Modify 33.12.1.2 [exec.run.loop.types] as indicated:

    -8- Let sndr be an expression of type run-loop-sender, let rcvr be an expression such that receiver-of<decltype((rcvr)), CS> is true where CS is the completion_signatures specialization above. Let C be either set_value_t or set_stopped_t. Then:

    • (8.1) — The expression connect(sndr, rcvr) has type run-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 if sndr is potentially-throwing, has type run-loop-scheduler, and compares equal to the run-loop-scheduler instance from which sndr was obtained.


4487. Is member 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".

However, it is not clear whether 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.

Previous resolution [SUPERSEDED]

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.

  1. Modify Table [tab:time.clock] — Cpp17Clock requirements as indicated:

    Table 131 — Cpp17Clock requirements [tab:time.clock]
    Expression Return type Operational semantics
    […]
    C1::is_steady const bool true if t1 <= t2 is always true and the time between
    clock ticks is constant,
    otherwise false.
    C1::is_steady shall be usable in constant expressions (7.7 [expr.const]).
    […]

Option B: Allowing varying between executions.

  1. Modify Table [tab:time.clock] — Cpp17Clock requirements as indicated:

    Table 131 — Cpp17Clock requirements [tab:time.clock]
    Expression Return type Operational semantics
    […]
    C1::is_steady const bool true if t1 <= t2 is always true and the time between
    clock ticks is constant
    for the duration of the current program execution,
    otherwise false.
    [Note ?: C1::is_steady is 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.

  1. Modify Table [tab:time.clock] — Cpp17Clock requirements as indicated:

    Table 131 — Cpp17Clock requirements [tab:time.clock]
    Expression Return type Operational semantics
    […]
    C1::is_steady const bool true if t1 <= t2 is always true and the time between
    clock ticks is constant,
    otherwise false.
    C1::is_steady shall be usable in constant expressions (7.7 [expr.const]).
    […]

4490. Allow calling std::ranges::size in ranges algorithms

Section: 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:

  1. 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.

  2. 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?"

Previous resolution [SUPERSEDED]

This wording is relative to N5032.

  1. Modify 26.2 [algorithms.requirements] as indicated:

    […]

    -14- Overloads of algorithms that take range arguments (25.4.2 [range.range]) behave as if they are implemented by dispatching to the overload in namespace ranges that takes separate iterator and sentinel arguments, where for each range argument r

    • (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), or ranges::next(ranges::begin(r), ranges::end(r)) if the type of r models forward_range and computing ranges::next meets the specified complexity requirements.one of the following:

      • (14.2.1) — if the type of r models forward_range and computing ranges::next meets the specified complexity requirements then

        • (14.2.1.1) — ranges::next(ranges::begin(r), ranges::size(r)) if r models sized_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.

  1. Modify 26.2 [algorithms.requirements] as indicated:

    […]

    -14- Overloads of algorithms that take range arguments (25.4.2 [range.range]) behave as if they are implemented by dispatching to the overload in namespace ranges that takes separate iterator and sentinel arguments, where for each range argument r

    • (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), or ranges::next(ranges::begin(r), ranges::end(r)) if the type of r models forward_range and computing ranges::next meets the specified complexity requirements. ranges::begin(r) + N where N is equal to ranges::distance(r).


4521. Improve [atomics.order] p10 to have a consistent way with [intro.races]

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 effect X 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, where X is 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 consent

Previous resolution [SUPERSEDED]

This 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:

  1. Modify 32.5.4 [atomics.order] as indicated:

    -10- An atomic read-modify-write operations shall always reads the last value from the side effect X, where X is 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:

  1. Modify 32.5.4 [atomics.order] as indicated:

    -10- An atomic read-modify-write operations shall always reads the last value 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. ]

  1. Modify 32.5.4 [atomics.order] as indicated:

    -10- An atomic read-modify-write operations shall always reads the last value 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.

Tentatively Ready Issues


4121. 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).

However, this is not the correct way to call 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."

Previous resolution [SUPERSEDED]

This wording is relative to N4986.

  1. Modify 25.5.7.1 [range.utility.conv.general] as indicated:

    -4- Let container-appendable be 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-append be 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);
}
Previous resolution [SUPERSEDED]

This wording is relative to N5001.

  1. Modify 25.5.7.1 [range.utility.conv.general] as indicated:

    -4- Let container-appendable be 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-append be 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.

  1. Modify 25.5.7.1 [range.utility.conv.general] as indicated:

    -4- Let container-appendable be 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-append be 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));
      };
    };
    

4531. Should there be a feature-test macro update for constexpr 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.

Given that P3391R2 contained the constexpr additions in P3438R0 which proposed to bump __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.

Previous resolution [SUPERSEDED]

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:

  1. Modify 17.3.2 [version.syn] as indicated:

    […]
    #define __cpp_lib_to_string                         202306L202511L // also in <string>
    […]
    

Option B:

  1. Modify 17.3.2 [version.syn] as indicated:

    […]
    #define __cpp_lib_constexpr_string                  201907L202511L // 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.

  1. Modify 17.3.2 [version.syn] as indicated:

    […]
    #define __cpp_lib_constexpr_string                  201907L202511L // also in <string>
    […]
    

4567. Feature test macro value for apply_result, is_applicable

Section: 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.

In Croydon we moved the P3795R2 "Miscellaneous Reflection Cleanup", that adds the reflection equivalents of above, under __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:

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.

  1. Modify 17.3.2 [version.syn] as indicated:

    #define __cpp_lib_apply 202506L202603L // freestanding, also in <tuple>, <type_traits>, <meta>
    

4568. std::execution::spawn_future is mishandling dependent senders

Section: 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-state be 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>()).

The problem happens in the definition of 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.

  1. Modify 33.9.12.18 [exec.spawn.future] as indicated:

    -7- Let spawn-future-state be 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>;
      […]
      };
    […]
    }
    

4579. make-state<Rcvr>::state-type::complete uses Env which is not in scope

Section: 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().

However, no declaration named 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 sends-stopped definition in bullet (15.3) is the only use of bare Env in this scope.

This issue was introduced by P3887R1 ("Make when_all a Ronseal Algorithm"), whose wording contains the same text.

[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.

  1. Modify 33.9.12.12 [exec.when.all] as indicated:

    -15- The member void state-type::complete(Rcvr& rcvr) noexcept behaves as follows:

    1. (15.1) — If disp is equal to disposition::started, evaluates: […]

    2. (15.2) — Otherwise, if disp is equal to disposition::error, evaluates: […]

    3. (15.3) — Otherwise, evaluates:

      if constexpr (sends-stopped) {
        on_stop.reset();
        set_stopped(std::move(rcvr));
      }
      

      where sends-stopped equals true if and only if there exists an element S of Sndrs such that completion_signatures_of_t<S, when-all-env<Envenv_of_t<Rcvr>>> contains set_stopped_t().