Revised 2025-10-27 at 13:06:59 UTC

Tentative Issues


2991(i). variant copy constructor missing noexcept(see below)

Section: 22.6.3.2 [variant.ctor] Status: Tentatively Ready Submitter: Peter Dimov Opened: 2017-06-27 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [variant.ctor].

View all other issues in [variant.ctor].

View all issues with Tentatively Ready status.

Discussion:

The copy constructor of std::variant is not conditionally noexcept (I think it was in the original proposal.)

It should be, for two reasons: first, this would be consistent with the other three constructors

constexpr variant() noexcept(see below);

variant(variant&&) noexcept(see below);

template <class T>
constexpr variant(T&&) noexcept(see below);

and second, variant itself makes use of is_nothrow_copy_constructible, so it's inconsistent for it to take a stance against it.

[2017-07 Toronto Tuesday PM issue prioritization]

Status to LEWG

[Wrocław 2024-11-18; LEWG approves the direction]

In P0088R1 the copy constructor was conditionally noexcept in the synopsis, but not the detailed description. This was pointed out during LWG review in Jacksonville. The approved paper, P008R3, doesn't have it in either place.

Previous resolution [SUPERSEDED]:

This wording is relative to N4659.

  1. Edit 22.6.3 [variant.variant], class template variant synopsis, as indicated:

    template <class... Types>
      class variant {
      public:
        // 23.7.3.1, constructors
        constexpr variant() noexcept(see below);
        variant(const variant&) noexcept(see below);
        variant(variant&&) noexcept(see below);
        […]
      };
    
  2. Edit 22.6.3.2 [variant.ctor] as indicated:

    variant(const variant& w) noexcept(see below);
    

    […]

    -8- Remarks: This function shall not participate in overload resolution unless is_copy_constructible_v<Ti> is true for all i. The expression inside noexcept is equivalent to the logical AND of is_nothrow_copy_constructible_v<Ti> for all i.

[2025-10-20; Jonathan provides updated wording]

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Proposed resolution:

This wording is relative to P5014.

  1. Edit 22.6.3 [variant.variant], class template variant synopsis, as indicated:

    template <class... Types>
      class variant {
      public:
        // 23.7.3.1, constructors
        constexpr variant() noexcept(see below);
        variant(const variant&) noexcept(see below);
        variant(variant&&) noexcept(see below);
        […]
      };
    
  2. Edit 22.6.3.2 [variant.ctor] as indicated:

    variant(const variant& w) noexcept(see below);
    

    […]

    -8- Remarks: This function is defined as deleted unless is_copy_constructible_v<Ti> is true for all i. The exception specification is equivalent to the logical and of is_nothrow_copy_constructible_v<Ti> for all i.


3090(i). What is §[time.duration.cons]p4's "no overflow is induced in the conversion" intended to mean?

Section: 30.5.2 [time.duration.cons] Status: Tentatively Ready Submitter: Richard Smith Opened: 2018-03-22 Last modified: 2025-10-23

Priority: 3

View all other issues in [time.duration.cons].

View all issues with Tentatively Ready status.

Discussion:

30.5.2 [time.duration.cons] p4 says:

template<class Rep2, class Period2>
  constexpr duration(const duration<Rep2, Period2>& d);

Remarks: This constructor shall not participate in overload resolution unless no overflow is induced in the conversion and treat_as_floating_point_v<rep> is true or both ratio_divide<Period2, period>::den is 1 and treat_as_floating_point_v<Rep2> is false.

with this example:

duration<int, milli> ms(3);
duration<int, micro> us = ms;  // OK
duration<int, milli> ms2 = us; // error

It's unclear to me what "no overflow is induced in the conversion" means in the above. What happens here:

duration<int, milli> ms(INT_MAX);
duration<int, micro> us = ms;  // ???

An overflow is clearly induced in the conversion here: internally, we'll multiply INT_MAX by 1000. But that cannot be determined statically (in general), and so can't affect the result of overload resolution.

So what's actually supposed to happen? Are we actually just supposed to check that Rep2 is no larger than Rep? (If so, what happens on overflow? Undefined behavior?)

It has been pointed out by Howard Hinnant:

This refers to the compile-time conversion factor to convert Period2 to Period. If that conversion factor is not representable as a (reduced) ratio<N, D>, then the constructor is SFINAE'd out. This might happen (for example) converting years to picoseconds.

I would not have guessed that from the wording. Maybe replacing "no overflow is induced in the conversion" with "the result of ratio_divide<Period2, Period> is representable as a ratio" or similar would help?

[2018-06-18 after reflector discussion]

Priority set to 3

[2020-09-12 Jonathan adds a proposed resolution]

Since the result of the ratio_divide has to be a ratio, if it's not representable then the result simply isn't a valid type. Implementations are not required to make ratio_divide SFINAE-friendly to implement this constraint. They can perform the equivalent calculations to check if they would overflow, without actually using ratio_divide.

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N4861.

  1. Modify 30.5.2 [time.duration.cons] as indicated:

    template<class Rep2, class Period2>
      constexpr duration(const duration<Rep2, Period2>& d);
    

    -3- Constraints: is_convertible_v<const Rep2&, rep> is true. ratio_divide<typename Period2::type, period> is a valid ratio specialization. Either:

    No overflow is induced in the conversion and treat_as_floating_point_v<rep> is true or both ratio_divide<Period2, period>::den is 1 and treat_as_floating_point_v<Rep2> is false. [Note: This requirement prevents implicit truncation errors when converting between integral-based duration types. Such a construction could easily lead to confusion about the value of the duration. — end note]


3627(i). Inconsistent specifications for std::make_optional overloads

Section: 22.5.10 [optional.specalg] Status: Tentatively Ready Submitter: Jiang An Opened: 2021-10-23 Last modified: 2025-10-16

Priority: 3

View all issues with Tentatively Ready status.

Discussion:

Three std::make_optional overloads are specified in 22.5.10 [optional.specalg]. The first one is specified by "Returns:" and the other two are specified by "Effects: Equivalent to:". According to 16.3.2.4 [structure.specifications]/4, such uses of "Effects: Equivalent to:" propagate the Constraints specified for constructors. As the selected constructor for the first overload has "Constraints:" (22.5.3.2 [optional.ctor]/22), it seems that inconsistency is introduced here.

Existing implementations are inconsistent: libstdc++ constrains all three overloads, while libc++ and MSVC STL do not constrain any of them.

IMO all three overloads should be constrained.

[2022-01-29; Reflector poll]

Set priority to 3 after reflector poll.

[2025-10-16; Status changed: New → Tentatively Ready.]

Reflector poll in 2024-07 with eight supporting votes.

Proposed resolution:

This wording is relative to N4901.

  1. Modify 22.5.10 [optional.specalg] as indicated:

    template<class T> constexpr optional<decay_t<T>> make_optional(T&& v);
    

    -3- ReturnsEffects: Equivalent to: return optional<decay_t<T>>(std::forward<T>(v));.


3908(i). enumerate_view::iterator constructor is explicit

Section: 25.7.24.3 [range.enumerate.iterator] Status: Tentatively NAD Submitter: Jonathan Wakely Opened: 2023-03-23 Last modified: 2024-06-24

Priority: Not Prioritized

View other active issues in [range.enumerate.iterator].

View all other issues in [range.enumerate.iterator].

View all issues with Tentatively NAD status.

Discussion:

enumerate_view::iterator has this constructor:

    constexpr explicit
      iterator(iterator_t<Base> current, difference_type pos);  // exposition only

In P2164R9 the detailed description of the function showed a default argument for the second parameter, which would justify it being explicit. However, that default argument was not present in the class synopsis and was removed from the detailed description when applying the paper to the draft.

[2023-06-01; Reflector poll]

Set status to Tentatively NAD after four votes in favour during reflector poll. The constructor is exposition-only, it doesn't make any difference to anything whether it's explicit or not.

Proposed resolution:

This wording is relative to N4944.

  1. Modify the class synopsis in 25.7.24.3 [range.enumerate.iterator] as shown:

    
        constexpr explicit
          iterator(iterator_t<Base> current, difference_type pos);  // exposition only
    
  2. Modify the detailed description in 25.7.24.3 [range.enumerate.iterator] as shown:

      constexpr explicit iterator(iterator_t<Base> current, difference_type pos);
    

    -2- Effects: Initializes current_ with std::move(current) and pos_ with pos.


3909(i). Issues about viewable_range

Section: 99 [ranges.refinements], 25.7.2 [range.adaptor.object] Status: Tentatively NAD Submitter: Jiang An Opened: 2023-03-27 Last modified: 2023-06-01

Priority: Not Prioritized

View all issues with Tentatively NAD status.

Discussion:

After LWG 3724(i), views::all is well-constrained for view types, and the constraints are stronger than viewable_range. The difference is that given an expression such that decltype gives R, when decay_t<R> is a view type and the implicit conversion of R to decay_t<R> is forbidden, views::all rejects the expression, but viewable_range may accept R. So I think we should remove the additional constraints on views::all_t.

While viewable_range is probably not introducing any additional constraint within the standard library, I think it is still useful to express the constraints on views::all, so it should be slightly adjusted to match views::all.

Furthermore, viewable_range is currently used in 25.7.2 [range.adaptor.object], but given P2378R3 relaxed the requirements for range adaptor closure objects, I think we should also apply similar relaxation for range adaptor objects. This should have no impact on standard range adaptor objects.

[2023-06-01; Reflector poll]

Set status to Tentatively NAD after three votes in favour during reflector poll.

"First change is pointless. Second change is a duplicate of 3896(i). Range adaptors return a view over their first argument, so they need to require it's a viewable_range."

Proposed resolution:

This wording is relative to N4944.

  1. Change the definition of views::all_t in 25.2 [ranges.syn] as indicated:

    
       template<viewable_rangeclass R>
          using all_t = decltype(all(declval<R>()));          // freestanding
    
  2. Change the definition of viewable_range in 25.4.6 [range.refinements] as indicated:

    -6- The viewable_range concept specifies the requirements of a range type that can be converted to a view safely.

    
    template<class T>
      concept viewable_range =
        range<T> &&
        ((view<remove_cvref_t<T>> && constructible_from<remove_cvref_t<T>, T> convertible_to<T, remove_cvref_t<T>>) ||
         (!view<remove_cvref_t<T>> &&
          (is_lvalue_reference_v<T> || (movable<remove_reference_t<T>> && !is-initializer-list<T>))));
    
  3. Change 25.7.2 [range.adaptor.object] as indicated:

    -6- A range adaptor object is a customization point object (16.3.3.3.5 [customization.point.object]) that accepts a viewable_rangerange as its first argument and returns a view.

    […]

    -8- If a range adaptor object adaptor accepts more than one argument, then let range be an expression such that decltype((range)) models viewable_rangerange, let args... be arguments such that adaptor(range, args...) is a well-formed expression as specified in the rest of subclause 25.7 [range.adaptors], and let BoundArgs be a pack that denotes decay_t<decltype((args))>.... The expression adaptor(args...) produces a range adaptor closure object f that is a perfect forwarding call wrapper (22.10.4 [func.require]) with the following properties: [...]


3958(i). ranges::to should prioritize the "reserve" branch

Section: 25.5.7.2 [range.utility.conv.to] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-07-17 Last modified: 2024-01-29

Priority: Not Prioritized

View other active issues in [range.utility.conv.to].

View all other issues in [range.utility.conv.to].

View all issues with Tentatively NAD status.

Discussion:

When the constructed range object has no range version constructor, ranges::to falls into a branch designed specifically for C++17-compliant containers, which calls the legacy constructor that accepts an iterator pair with C(ranges::begin(r), ranges::end(r), std::forward<Args>(args)...).

However, this kind of initialization may bring some performance issues, because we split the original range into pairs of iterators, which may lose information contained in the original range, for example:

#include <boost/container/vector.hpp>
#include <sstream>
#include <ranges>

int main() {
  std::istringstream ints("1 2 3 4 5");
  std::ranges::subrange s(std::istream_iterator<int>(ints),
                          std::istream_iterator<int>(),
                          5);
  auto r = std::ranges::to<boost::container::vector>(s); // discard size info
}

Above, subrange saves the size information of the stream, but ranges::to only extracts its iterator pair to create the object, so that the original size information is discarded, resulting in unnecessary allocations.

I believe we should prefer to use the "reserve" branch here because it is really designed for this situation.

[2023-10-30; Reflector poll]

Set status to Tentatively NAD after reflector poll. "This optimizes exotic cases at the expense of realistic cases."

Proposed resolution:

This wording is relative to N4950.

  1. Modify 25.5.7.2 [range.utility.conv.to] as indicated:

    template<class C, input_range R, class... Args> requires (!view<C>)
      constexpr C to(R&& r, Args&&... args);
    

    -1- Mandates: C is a cv-unqualified class type.

    -2- Returns: An object of type C constructed from the elements of r in the following manner:

    1. (2.1) — If C does not satisfy input_range or convertible_to<range_reference_t<R>, range_value_t<C>> is true:

      1. (2.1.1) — If constructible_from<C, R, Args...> is true:

        C(std::forward<R>(r), std::forward<Args>(args)...)
      2. (2.1.2) — Otherwise, if constructible_from<C, from_range_t, R, Args...> is true:

        C(from_range, std::forward<R>(r), std::forward<Args>(args)...)
      3. (2.1.3) — Otherwise, if

        1. (2.1.3.1) — common_range<R> is true,

        2. (2.1.3.2) — the qualified-id iterator_traits<iterator_t<R>>::iterator_category is valid and denotes a type that models derived_from<input_iterator_tag>, and

        3. (2.1.3.3) — constructible_from<C, iterator_t<R>, sentinel_t<R>, Args...> is true:

          C(ranges::begin(r), ranges::end(r), std::forward<Args>(args)...)
      4. (2.1.4) — Otherwise, if

        1. (2.1.4.1) — constructible_from<C, Args...> is true, and

        2. (2.1.4.2) — container-insertable<C, range_reference_t<R>> is true:

          C c(std::forward<Args>(args)...);
          if constexpr (sized_range<R> && reservable-container<C>)
            c.reserve(static_cast<range_size_t<C>>(ranges::size(r)));
          ranges::copy(r, container-inserter<range_reference_t<R>>(c));
          
      5. (?.?.?) — Otherwise, if

        1. (?.?.?.?) — common_range<R> is true,

        2. (?.?.?.?) — the qualified-id iterator_traits<iterator_t<R>>::iterator_category is valid and denotes a type that models derived_from<input_iterator_tag>, and

        3. (?.?.?.?) — constructible_from<C, iterator_t<R>, sentinel_t<R>, Args...> is true:

          C(ranges::begin(r), ranges::end(r), std::forward<Args>(args)...)
    2. (2.2) — Otherwise, if input_range<range_reference_t<R>> is true:

      to<C>(r | views::transform([](auto&& elem) {
        return to<range_value_t<C>>(std::forward<decltype(elem)>(elem));
      }), std::forward<Args>(args)...);
      
    3. (2.3) — Otherwise, the program is ill-formed.


3980(i). The read exclusive ownership of an atomic read-modify-write operation and whether its read and write are two operations are unclear

Section: 32.5.4 [atomics.order] Status: Tentatively NAD Submitter: jim x Opened: 2023-08-22 Last modified: 2023-11-03

Priority: Not Prioritized

View other active issues in [atomics.order].

View all other issues in [atomics.order].

View all issues with Tentatively NAD status.

Discussion:

Such two questions are sourced from StackOverflow:

  1. Can the read operations in compare_exchange_strong in different two thread read the same value?

  2. For purposes of ordering, is atomic read-modify-write one operation or two?

Given this example:

#include <iostream>
#include <atomic>
#include <thread>

struct SpinLock{
  std::atomic<bool> atomic_;
  void lock(){
    bool expected = false;
    while (!atomic_.compare_exchange_strong(expected,true,std::memory_order_release,std::memory_order_relaxed)) {
    }
  }
  void unlock(){
    atomic_.store(false, std::memory_order_release);
  }
};

int main(){
  SpinLock spin{false};
  auto t1 = std::thread([&](){
    spin.lock();
    spin.unlock();
  });
  auto t2 = std::thread([&](){
    spin.lock();
    spin.unlock();
  });
  t1.join();
  t2.join();
}

In the current draft, the relevant phrasing that can interpret that only one read-modify-write operation reads the initial value false is 32.5.4 [atomics.order] p10:

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.

However, the wording can have two meanings, each kind of read can result in different explanations for the example

  1. The check of the violation is done before the side effect of the RMW is in the modification order, i.e. the rule is just checked at the read point.

  2. The check of the violation is done after the side effect of the RMW is in the modification order, i.e. the rule is checked when RMW tries to add the side effect that is based on the read-value to the modification order, and that side effect wouldn't be added to the modification order if the rule was violated.

With the first interpretation, the two RMW operations can read the same initial value because that value is indeed the last value in the modification order before such two RMW operations produce the side effect to the modification order.

With the second interpretation, there is only one RMW operation that can read the initial value because the latter one in the modification order would violate the rule if it read the initial value.

Such two interpretations arise from that the wording doesn't clearly specify when that check is performed.

So, my proposed wording is:

Atomic read-modify-write operations shall always read the value from a side effect X, where X immediately precedes the side effect of the read-modify-write operation in the modification order.

This wording keeps a similar utterance to 6.10.2.2 [intro.races], and it can clearly convey the meaning that we say the value read by RWM is associated with the side effect of RMW in the modification order.

Relevant discussion can be seen CWG/issues/423 here.

[2023-11-03; Reflector poll]

NAD. The first reading isn't plausible.

Proposed resolution:

This wording is relative to N4958.

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

    -10- Atomic read-modify-write operations shall always read the last value from a side effect X, where X 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.

    -11- Implementations should make atomic stores visible to atomic loads within a reasonable amount of time.


3981(i). Range adaptor closure object is underspecified for its return type

Section: 25.7.2 [range.adaptor.object] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-08-22 Last modified: 2024-06-24

Priority: Not Prioritized

View other active issues in [range.adaptor.object].

View all other issues in [range.adaptor.object].

View all issues with Tentatively NAD status.

Discussion:

In order to provide pipe support for user-defined range adaptors, P2387R3 removed the specification that the adaptor closure object returns a view, which conforms to the wording of ranges::to.

However, the current wording seems to be too low-spec so that the range adaptor closure object can return any type or even void. This makes it possible to break the previous specification when returning types that don't make sense, for example:

#include <ranges>

struct Closure : std::ranges::range_adaptor_closure<Closure> {
  struct NonCopyable {
    NonCopyable(const NonCopyable&) = delete;
  };

  const NonCopyable& operator()(std::ranges::range auto&&);
};

auto r = std::views::iota(0) | Closure{}; // hard error in libstdc++ and MSVC-STL

Above, since the return type of the pipeline operator is declared as auto, this causes the deleted copy constructor to be invoked in the function body and produces a hard error.

The proposed resolution adds a specification for the range adaptor closure object to return a cv-unqualified class type.

[2023-10-30; Reflector poll]

Set status to Tentatively NAD. "The wording says R | C is equivalent to C(R), not auto(C(R))."

Proposed resolution:

This wording is relative to N4958.

  1. Modify 25.7.2 [range.adaptor.object] as indicated:

    -1- A range adaptor closure object is a unary function object that accepts a range argument. For a range adaptor closure object C and an expression R such that decltype((R)) models range, the following expressions are equivalent:

    […]

    -2- Given an object t of type T, where

    1. (2.1) — t is a unary function object that accepts a range argument and returns a cv-unqualified class object,

    2. […]

    then the implementation ensures that t is a range adaptor closure object.


3982(i). is-derived-from-view-interface should require that T is derived from view_interface<T>

Section: 25.4.5 [range.view] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-08-22 Last modified: 2023-10-30

Priority: Not Prioritized

View all other issues in [range.view].

View all issues with Tentatively NAD status.

Discussion:

Currently, the wording of is-derived-from-view-interface only detects whether type T is unambiguously derived from one base class view_interface<U> where U is not required to be T, which is not the intention of CRTP.

[2023-10-30; Reflector poll]

Set status to Tentatively NAD. The wording correctly handles the case where T derives from Base which derives from view_interface<Base>. We don't want it to only be satisfied for direct inheritance from view_interface<T>, but from any specialization of view_interface. Previously the concept only checked for inheritance from view_base but it was changed when view_interface stopped inheriting from view_base.

Proposed resolution:

This wording is relative to N4958.

  1. Modify 25.4.5 [range.view] as indicated:

    template<class T>
      constexpr bool is-derived-from-view-interface = see below;            // exposition only
    template<class T>
      constexpr bool enable_view =
        derived_from<T, view_base> || is-derived-from-view-interface<T>;
    

    -6- For a type T, is-derived-from-view-interface<T> is true if and only if T has exactly one public base class view_interface<TU> for some type U and T has no base classes of type view_interface<UV> for any other type UV.


3992(i). basic_stringbuf::str()&& should enforce 𝒪(1)

Section: 31.8.2.4 [stringbuf.members] Status: Tentatively NAD Submitter: Peter Sommerlad Opened: 2023-10-05 Last modified: 2025-10-24

Priority: 4

View all other issues in [stringbuf.members].

View all issues with Tentatively NAD status.

Discussion:

Recent discussions on llvm-64644 came to the conclusion that basic_stringbuf() && introduced by P0408 might just copy the underlying buffer into a string object and not actually move the allocated space. While the wording tried to encourage that, especially with the postcondition that the buffer must be empty afterwards, it failed to specify that the move is never a copy.

I suggest to amend the specification to enforce implementors to do the 𝒪(1) thing. There might be ABI issues for those who still copy.

Some investigation into 23.2.2.2 [container.reqmts] p.16 and 27.4.3.1 [basic.string.general] shows that a basic_string as a standard container should move with 𝒪(1).

Unfortunately, we cannot say

str().data() == buf.data() before calling str()

as a postcondition due to SSO. Maybe a note could be added to eliminate the confusion.

[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]

The requirements are already implied, and proposed wording has no effect.

Proposed resolution:

This wording is relative to N4958.

  1. Modify 31.8.2.4 [stringbuf.members] as indicated:

    basic_string<charT, traits, Allocator> str() &&;
    

    -9- Postconditions: The underlying character sequence buf is empty and pbase(), pptr(), epptr(), eback(), gptr(), and egptr() are initialized as if by calling init_buf_ptrs() with an empty buf.

    -10- Returns: A basic_string<charT, traits, Allocator> object move constructed from the basic_stringbuf's underlying character sequence in buf. This can be achieved by first adjusting buf to have the same content as view().

    [Note: — 23.2.2.2 [container.reqmts] require the move construction of the return value to be 𝒪(1) end note]


4003(i). view_interface::back is overconstrained

Section: 25.5.3 [view.interface] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-10-28 Last modified: 2024-06-24

Priority: Not Prioritized

View all other issues in [view.interface].

View all issues with Tentatively NAD status.

Discussion:

Currently, view_interface only provides the back member when the derived class satisfies both bidirectional_range and common_range, which ensures that ranges::prev can act its sentinel.

However, requiring common_range seems to be too strict because when the derived class satisfies both random_access_range and sized_range, its end iterator can still be calculated in constant time, which is what some range adaptors currently do to greedily become common ranges.

I think we should follow similar rules to eliminate this inconsistency (demo):

#include <ranges>

constexpr auto r = std::ranges::subrange(std::views::iota(0), 5);
constexpr auto z = std::views::zip(r);
static_assert(r.back() == 4); // ill-formed
static_assert(std::get<0>(z.back()) == 4); // ok

[2023-11-07; Reflector poll]

NAD. "During the concat discussion LEWG decided not to support the corner case of random-access sized but not-common ranges." "If we did want to address such ranges, would be better to enforce commonness for random-access sized ranges by having ranges::end return ranges::begin(r) + ranges::size(r)."

Proposed resolution:

This wording is relative to N4964.

  1. Modify 25.5.3 [view.interface], class template view_interface synopsis, as indicated:

    namespace std::ranges {
      template<class D>
        requires is_class_v<D> && same_as<D, remove_cv_t<D>>
      class view_interface {
        […]
      public:
        […]
        constexpr decltype(auto) back() requires (bidirectional_range<D> && common_range<D>) ||
                                                 (random_access_range<D> && sized_range<D>);
        constexpr decltype(auto) back() const
          requires (bidirectional_range<const D> && common_range<const D>) ||
                   (random_access_range<const D> && sized_range<const D>);
        […]
      };
    }
    
  2. Modify 25.5.3.2 [view.interface.members] as indicated:

    constexpr decltype(auto) back() requires (bidirectional_range<D> && common_range<D>) ||
                                             (random_access_range<D> && sized_range<D>);
    constexpr decltype(auto) back() const
      requires (bidirectional_range<const D> && common_range<const D>) ||
               (random_access_range<const D> && sized_range<const D>);
    

    -3- Preconditions: !empty() is true.

    -4- Effects: Equivalent to:

    auto common-arg-end = []<class R>(R& r) {
      if constexpr (common_range<R>) {
        return ranges::end(r);
      } else {
        return ranges::begin(r) + ranges::distance(r);
      }
    };
    return *ranges::prev(common-arg-endranges::end(derived()));
    

4006(i). chunk_view::outer-iterator::value_type should provide empty

Section: 25.7.29.4 [range.chunk.outer.value] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-11-05 Last modified: 2024-03-11

Priority: Not Prioritized

View all other issues in [range.chunk.outer.value].

View all issues with Tentatively NAD status.

Discussion:

chunk_view::outer-iterator::value_type can determine whether it is empty by simply checking whether the chunk_view's remainder_ is 0, which makes it valuable to explicitly provide a noexcept empty member.

Otherwise, the view_interface::empty is synthesized only through the size member when the original sentinel and iterator type model sized_sentinel_for, which seems overkill:

#include <cassert>
#include <iostream>
#include <sstream>
#include <ranges>

int main() {
  auto ints = std::istringstream{"1 2 3 4 5 6 7 8 9 10"};
  for (auto chunk : std::views::istream<int>(ints) | std::views::chunk(3)) {
    for (auto elem : chunk) {
      assert(!chunk.empty()); // no matching function for call to 'empty()'
      std::cout << elem << " ";
    }
    assert(chunk.empty()); // ditto
    std::cout << "\n";
  }
}

[2024-03-11; Reflector poll]

Set status to Tentatively NAD after reflector poll in November 2023.

"The example shows you could use it if it existed, but not why that would be useful."

"This is a bad idea - the fact that the chunk 'shrinks' as it is iterated over is an implementation detail and not supposed to be observable."

Proposed resolution:

This wording is relative to N4964.

  1. Modify 25.7.29.4 [range.chunk.outer.value] as indicated:

      namespace std::ranges {
        template<view V>
          requires input_range<V>
        struct chunk_view<V>::outer-iterator::value_type : view_interface<value_type> {
        private:
          chunk_view* parent_;                                        // exposition only
    
          constexpr explicit value_type(chunk_view& parent);          // exposition only
    
        public:
          constexpr inner-iterator begin() const noexcept;
          constexpr default_sentinel_t end() const noexcept;
    
          constexpr bool empty() const noexcept;
          constexpr auto size() const
            requires sized_sentinel_for<sentinel_t<V>, iterator_t<V>>;
        };
      }
    
    […]
    constexpr default_sentinel_t end() const noexcept;
    

    -3- Returns: default_sentinel.

    constexpr bool empty() const noexcept;
    

    -?- Effects: Equivalent to: return parent_->remainder_ == 0;


4009(i). drop_view::begin const may have 𝒪(n) complexity

Section: 25.7.12.2 [range.drop.view] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-11-08 Last modified: 2025-10-22

Priority: 3

View other active issues in [range.drop.view].

View all other issues in [range.drop.view].

View all issues with Tentatively NAD status.

Discussion:

drop_view::begin const is specified to return ranges::next(ranges::begin(base_), count_, ranges::end(base_)), which has 𝒪(n) complexity when base_ is a random-access-sized but non-common range (demo):

#include <ranges>

int main() {
  const auto s = std::ranges::subrange(std::views::iota(0uz), size_t(-1));
  const auto r = std::ranges::drop_view(s, s.size() - 1);
  const auto b = r.begin(); // time out
}

[2025-10-22; Reflector poll. Status changed: New → Tentatively NAD]

Set priority to 3 after reflector poll, status to Tentatively NAD.

"NAD, it's Returns: not 'Effects: Equivalent to ...', implementations need to implement it to return that while meeting the amortized 𝒪(1) guarantee."

Proposed resolution:

This wording is relative to N4964.

  1. Modify 25.7.12.2 [range.drop.view] as indicated:

    constexpr auto begin()
      requires (!(simple-view<V> &&
                  random_access_range<const V> && sized_range<const V>));
    constexpr auto begin() const
      requires random_access_range<const V> && sized_range<const V>;
    

    -3- Returns:

    1. (?.?) — If V models random_access_range and sized_range,

      ranges::begin(base_) + (ranges::distance(base_) - range_difference_t<V>(size()))
      
    2. (?.?) — Otherwise, ranges::next(ranges::begin(base_), count_, ranges::end(base_)).

    -4- Remarks: In order to provide the amortized constant-time complexity required by the range concept when Vdrop_view does not models random_access_range and sized_rangeforward_range, this functionthe first overload caches the result within the drop_view for use on subsequent calls.

    [Note 1: Without this, applying a reverse_view over a drop_view would have quadratic iteration complexity. — end note]

    
    constexpr auto begin() const
      requires random_access_range<const V> && sized_range<const V>;
    

    -?- Returns: ranges::begin(base_) + (ranges::distance(base_) - range_difference_t<const V>(size())).


4020(i). extents::index-cast weirdness

Section: 23.7.3.3.2 [mdspan.extents.expo] Status: Tentatively Ready Submitter: Casey Carter Opened: 2023-11-29 Last modified: 2025-10-17

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

The exposition-only static member index-cast of extents is specified as (23.7.3.3.2 [mdspan.extents.expo]/9):

template<class OtherIndexType>
static constexpr auto index-cast(OtherIndexType&& i) noexcept;

-9- Effects:

  1. (9.1) — If OtherIndexType is an integral type other than bool, then equivalent to return i;,

  2. (9.2) — otherwise, equivalent to return static_cast<index_type>(i);.

[Note 1: This function will always return an integral type other than bool. Since this function's call sites are constrained on convertibility of OtherIndexType to index_type, integer-class types can use the static_cast branch without loss of precision. — end note]

This function returns T when passed an rvalue of cv-unqualified integral type T, but index_type when passed a cv-qualified and/or lvalue argument of any integral type. It would seem more consistent and easier to reason about if 9.1 was instead conditional on remove_cvref_t<OtherIndexType>.

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after eight votes in favour during reflector poll.

"Doesn't matter in this case, but logically decay_t seems like a better fit."

Proposed resolution:

This wording is relative to N4964.

  1. Modify 23.7.3.3.2 [mdspan.extents.expo] as indicated:

    template<class OtherIndexType>
      static constexpr auto index-cast(OtherIndexType&& i) noexcept;
    

    -9- Effects:

    1. (9.1) — If remove_cvref_t<OtherIndexType> is an integral type other than bool, then equivalent to return i;,

    2. (9.2) — otherwise, equivalent to return static_cast<index_type>(i);.

    [Note 1: This function will always return an integral type other than bool. Since this function's call sites are constrained on convertibility of OtherIndexType to index_type, integer-class types can use the static_cast branch without loss of precision. — end note]


4050(i). Should views::iota(0) | views::take(5) be views::iota(0, 5)?

Section: 25.7.10.1 [range.take.overview], 25.7.10.1 [range.take.overview] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2024-01-28 Last modified: 2025-10-20

Priority: Not Prioritized

View other active issues in [range.take.overview].

View all other issues in [range.take.overview].

View all issues with Tentatively NAD status.

Discussion:

Given that C++20 ranges does not introduce the infinite range notification present in range/v3, this means that views::iota(0) | views::take(5) will currently return a take_view object that does not model sized_range.

However, with the introduction of C++23 repeat_view, its interaction with views::take/drop does have special handling depending on whether it is an infinite range, which causes views::repeat(0) | views::take(5) to return a repeat_view objects that satisfy sized_range.

This inconsistency leads to very different behavior of these two range factories in the case of infinite ranges (demo):

#include <ranges>

auto take_and_drop = std::views::drop(5)
                   | std::views::take(4)
                   | std::views::drop(3)
                   | std::views::take(2)
                   | std::views::drop(1);

// The type of iota is drop_view<take_view<drop_view<take_view<drop_view<iota_view<int, unreachable_sentinel_t>>>>>>, which is indeed a template bloat.
auto iota = std::views::iota(0) | take_and_drop;
static_assert(std::ranges::sized_range<decltype(iota)>); // failed

// The type of repeat is simply std::ranges::repeat_view<int, long>
std::ranges::sized_range auto repeat = std::views::repeat(0) | take_and_drop; // ok

If we do account for the infinity of repeat_view, then I see no reason not to do it for iota_view, as this is obviously intuitive and can indeed be considered an enhancement.

[2025-10-20; Reflector poll; Status changed: New → Tentatively NAD.]

"This changes meaning of existing C++20 for unclear benefit. This would need a paper."

"Why does iota(0, 10) | take(5) give you iota(0, 5) but iota(0) | take(5) doesn't?"

"IIRC there was opposition to P1739 introducing any kind of special cases in the adaptor objects. What got consensus was only the 'specialisations' that preserve the exact type of the underlying range. Thus iota(0, 10)iota(0, 5) was fine, but iota(0)iota(0, 5) would not have been. I still think that all changes that simplify the return types are helpful, but it would certainly be a breaking change now."

Proposed resolution:

This wording is relative to N4971.

  1. Modify 25.7.10.1 [range.take.overview] as indicated:

    -2- The name views::take denotes a range adaptor object (25.7.2 [range.adaptor.object]). Let E and F be expressions, let T be remove_cvref_t<decltype((E))>, and let D be range_difference_t<decltype((E))>. If decltype((F)) does not model convertible_to<D>, views::take(E, F) is ill-formed. Otherwise, the expression views::take(E, F) is expression-equivalent to:

    1. (2.1) — if T is a specialization of empty_view (25.6.2.2 [range.empty.view]), then ((void)F, decay-copy(E)), except that the evaluations of E and F are indeterminately sequenced.

    2. (2.2) — Otherwise, if T models random_access_range and sized_range and is a specialization of span (23.7.2.2 [views.span]), basic_string_view (27.3 [string.view]), or ranges::subrange (25.5.4 [range.subrange]), then U(ranges::begin(E), ranges::begin(E) + std::min<D>(ranges::distance(E), F)), except that E is evaluated only once, where U is a type determined as follows:

      1. (2.2.1) — if T is a specialization of span, then U is span<typename T::element_type>;

      2. (2.2.2) — otherwise, if T is a specialization of basic_string_view, then U is T;

      3. (2.2.3) — otherwise, T is a specialization of subrange, and U is subrange<iterator_t<T>>;

    3. (2.3) — otherwise, if T is a specialization of iota_view (25.6.4.2 [range.iota.view]) that models random_access_range and sized_range, then iota_view(*ranges::begin(E), *(ranges::begin(E) + std::min<D>(ranges::distance(E), F))), except that E is evaluated only once.

    4. (2.?) — Otherwise, if T is a specialization of iota_view that models random_access_range and same_as<sentinel_t<T>, unreachable_sentinel_t> is true, then views::iota(*ranges::begin(E), *(ranges::begin(E) + static_cast<D>(F))), except that E is evaluated only once.

    5. (2.4) — Otherwise, if T is a specialization of repeat_view (25.6.5.2 [range.repeat.view]):

      1. (2.4.1) — if T models sized_range, then

          views::repeat(*E.value_, std::min<D>(ranges::distance(E), F))
        except that E is evaluated only once;

      2. (2.4.2) — otherwise, views::repeat(*E.value_, static_cast<D>(F)).

    6. (2.5) — Otherwise, take_view(E, F).

  2. Modify 25.7.12.1 [range.drop.overview] as indicated:

    -2- The name views::drop denotes a range adaptor object (25.7.2 [range.adaptor.object]). Let E and F be expressions, let T be remove_cvref_t<decltype((E))>, and let D be range_difference_t<decltype((E))>. If decltype((F)) does not model convertible_to<D>, views::drop(E, F) is ill-formed. Otherwise, the expression views::drop(E, F) is expression-equivalent to:

    1. (2.1) — if T is a specialization of empty_view (25.6.2.2 [range.empty.view]), then ((void)F, decay-copy(E)), except that the evaluations of E and F are indeterminately sequenced.

    2. (2.2) — Otherwise, if T models random_access_range and sized_range and is

      1. (2.2.1) — a specialization of span (23.7.2.2 [views.span]),

      2. (2.2.2) — a specialization of basic_string_view (27.3 [string.view]),

      3. (2.2.3) — a specialization of iota_view (25.6.4.2 [range.iota.view]), or

      4. (2.2.4) — a specialization of subrange (25.5.4 [range.subrange]) where T::StoreSize is false,

      then U(ranges::begin(E) + std::min<D>(ranges::distance(E), F), ranges::end(E)), except that E is evaluated only once, where U is span<typename T::element_type> if T is a specialization of span and T otherwise.

    3. (2.?) — Otherwise, if T is a specialization of iota_view that models random_access_range and same_as<sentinel_t<T>, unreachable_sentinel_t> is true, then views::iota(*(ranges::begin(E) + static_cast<D>(F))).

    4. (2.3) — Otherwise, if T is a specialization of subrange (25.5.4 [range.subrange]) that models random_access_range and sized_range, then T(ranges::begin(E) + std::min<D>(ranges::distance(E), F), ranges::end(E), to-unsigned-like(ranges::distance(E) - std::min<D>(ranges::distance(E), F))), except that E and F are each evaluated only once.

    5. (2.4) — Otherwise, if T is a specialization of repeat_view (25.6.5.2 [range.repeat.view]):

      1. (2.4.1) — if T models sized_range, then

          views::repeat(*E.value_, ranges::distance(E) - std::min<D>(ranges::distance(E), F))
        except that E is evaluated only once;

      2. (2.4.2) — otherwise, ((void)F, decay-copy(E)), except that the evaluations of E and F are indeterminately sequenced.

    6. (2.5) — Otherwise, drop_view(E, F).


4095(i). ranges::fold_meow should explicitly spell out the return type

Section: 26.4 [algorithm.syn], 26.6.18 [alg.fold] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2024-05-03 Last modified: 2024-06-24

Priority: Not Prioritized

View other active issues in [algorithm.syn].

View all other issues in [algorithm.syn].

View all issues with Tentatively NAD status.

Discussion:

Unlike other algorithms, the return types of ranges::fold_meow are specified in terms of auto and see below, and its implementation details depend on the return types of other overloads through decltype(fold_meow(...)).

This makes determining the return type of a certain overload (such as fold_right_last) extremely difficult even for experts, requiring several trips back and forth to different overloads to finally understand what the actual return type is. The situation is even worse for newbies because such a form of specifying the return type makes it impossible for the IDE to deduce the real return type, which is extremely user-unfriendly.

I think that explicitly specifying the return type for these overloads not only greatly improves readability but also offloads the compiler from deducing the return type, which can definitely be considered an improvement.

The proposed resolution does not touch the Effects clause and only changes the function signature to seek minimal changes.

[2024-06-24; Reflector poll: NAD]

Implementations are free to spell this out if desired.

Proposed resolution:

This wording is relative to N4981.

  1. Modify 26.4 [algorithm.syn], header <algorithm> synopsis, as indicated:

    #include <initializer_list>     // see 17.11.2 [initializer.list.syn]
    
    namespace std {
      […]
      namespace ranges {
        […]
        template<input_iterator I, sentinel_for<I> S, class T = iter_value_t<I>,
                 indirectly-binary-left-foldable<T, I> F>
          constexpr auto fold_left(I first, S last, T init, F f) ->
            decay_t<invoke_result_t<F&, T, iter_reference_t<I>>>;
    
        template<input_range R, class T = range_value_t<R>,
                 indirectly-binary-left-foldable<T, iterator_t<R>> F>
          constexpr auto fold_left(R&& r, T init, F f) ->
            decay_t<invoke_result_t<F&, T, range_reference_t<R>>>;
    
        template<input_iterator I, sentinel_for<I> S,
                 indirectly-binary-left-foldable<iter_value_t<I>, I> F>
          requires constructible_from<iter_value_t<I>, iter_reference_t<I>>
          constexpr auto fold_left_first(I first, S last, F f) ->
            optional<decay_t<invoke_result_t<F&, iter_value_t<I>, iter_reference_t<I>>>>;
    
        template<input_range R, indirectly-binary-left-foldable<range_value_t<R>, iterator_t<R>> F>
          requires constructible_from<range_value_t<R>, range_reference_t<R>>
          constexpr auto fold_left_first(R&& r, F f) ->
            optional<decay_t<invoke_result_t<F&, range_value_t<R>, range_reference_t<R>>>>;
    
        template<bidirectional_iterator I, sentinel_for<I> S, class T = iter_value_t<I>,
                 indirectly-binary-right-foldable<T, I> F>
          constexpr auto fold_right(I first, S last, T init, F f) ->
            decay_t<invoke_result_t<F&, iter_reference_t<I>, T>>;
    
        template<bidirectional_range R, class T = range_value_t<R>,
                 indirectly-binary-right-foldable<T, iterator_t<R>> F>
          constexpr auto fold_right(R&& r, T init, F f) ->
            decay_t<invoke_result_t<F&, range_reference_t<R>, T>>;
    
        template<bidirectional_iterator I, sentinel_for<I> S,
                 indirectly-binary-right-foldable<iter_value_t<I>, I> F>
          requires constructible_from<iter_value_t<I>, iter_reference_t<I>>
        constexpr auto fold_right_last(I first, S last, F f) ->
          optional<decay_t<invoke_result_t<F&, iter_reference_t<I>, iter_value_t<I>>>>;
    
        template<bidirectional_range R,
                 indirectly-binary-right-foldable<range_value_t<R>, iterator_t<R>> F>
          requires constructible_from<range_value_t<R>, range_reference_t<R>>
        constexpr auto fold_right_last(R&& r, F f) ->
          optional<decay_t<invoke_result_t<F&, range_reference_t<R>, range_value_t<R>>>>;
    
        template<class I, class T>
          using fold_left_with_iter_result = in_value_result<I, T>;
        template<class I, class T>
          using fold_left_first_with_iter_result = in_value_result<I, T>;
    
        template<input_iterator I, sentinel_for<I> S, class T = iter_value_t<I>,
                 indirectly-binary-left-foldable<T, I> F>
          constexpr see belowauto fold_left_with_iter(I first, S last, T init, F f) ->
            fold_left_with_iter_result<I, decay_t<invoke_result_t<F&, T, iter_reference_t<I>>>>;
    
        template<input_range R, class T = range_value_t<R>,
                 indirectly-binary-left-foldable<T, iterator_t<R>> F>
          constexpr see belowauto fold_left_with_iter(R&& r, T init, F f) ->
            fold_left_with_iter_result<borrowed_iterator_t<R>,
                                       decay_t<invoke_result_t<F&, T, range_reference_t<R>>>>;
    
        template<input_iterator I, sentinel_for<I> S,
                 indirectly-binary-left-foldable<iter_value_t<I>, I> F>
          requires constructible_from<iter_value_t<I>, iter_reference_t<I>>
          constexpr see belowauto fold_left_first_with_iter(I first, S last, F f) ->
            fold_left_first_with_iter_result<
              I, optional<decay_t<invoke_result_t<F&, iter_value_t<I>, iter_reference_t<I>>>>>;
    
        template<input_range R,
                 indirectly-binary-left-foldable<range_value_t<R>, iterator_t<R>> F>
          requires constructible_from<range_value_t<R>, range_reference_t<R>>
          constexpr see belowauto fold_left_first_with_iter(R&& r, F f) ->
            fold_left_first_with_iter_result<
              borrowed_iterator_t<R>,
              optional<decay_t<invoke_result_t<F&, range_value_t<R>, range_reference_t<R>>>>>;
      }
      […]
    }
    
  2. Modify 26.6.18 [alg.fold] as indicated:

    template<input_iterator I, sentinel_for<I> S, class T = iter_value_t<I>,
             indirectly-binary-left-foldable<T, I> F>
    constexpr auto ranges::fold_left(I first, S last, T init, F f) ->
      decay_t<invoke_result_t<F&, T, iter_reference_t<I>>>;
    
    template<input_range R, class T = range_value_t<R>,
             indirectly-binary-left-foldable<T, iterator_t<R>> F>
    constexpr auto ranges::fold_left(R&& r, T init, F f) ->
      decay_t<invoke_result_t<F&, T, range_reference_t<R>>>;
    

    -1- Returns:

    ranges::fold_left_with_iter(std::move(first), last, std::move(init), f).value
    
    template<input_iterator I, sentinel_for<I> S,
             indirectly-binary-left-foldable<iter_value_t<I>, I> F>
      requires constructible_from<iter_value_t<I>, iter_reference_t<I>>
      constexpr auto ranges::fold_left_first(I first, S last, F f) ->
        optional<decay_t<invoke_result_t<F&, iter_value_t<I>, iter_reference_t<I>>>>;
    
    template<input_range R, indirectly-binary-left-foldable<range_value_t<R>, iterator_t<R>> F>
      requires constructible_from<range_value_t<R>, range_reference_t<R>>
      constexpr auto ranges::fold_left_first(R&& r, F f) ->
        optional<decay_t<invoke_result_t<F&, range_value_t<R>, range_reference_t<R>>>>;
    

    -2- Returns:

    ranges::fold_left_first_with_iter(std::move(first), last, f).value
    
    template<bidirectional_iterator I, sentinel_for<I> S, class T = iter_value_t<I>,
             indirectly-binary-right-foldable<T, I> F>
      constexpr auto ranges::fold_right(I first, S last, T init, F f) ->
        decay_t<invoke_result_t<F&, iter_reference_t<I>, T>>;
    
    template<bidirectional_range R, class T = range_value_t<R>,
            indirectly-binary-right-foldable<T, iterator_t<R>> F>
      constexpr auto ranges::fold_right(R&& r, T init, F f) ->
        decay_t<invoke_result_t<F&, range_reference_t<R>, T>>;  
    

    -3- Effects: Equivalent to:

    using U = decay_t<invoke_result_t<F&, iter_reference_t<I>, T>>;
    if (first == last)
      return U(std::move(init));
    I tail = ranges::next(first, last);
    U accum = invoke(f, *--tail, std::move(init));
    while (first != tail)
      accum = invoke(f, *--tail, std::move(accum));
    return accum;
    
    template<bidirectional_iterator I, sentinel_for<I> S,
            indirectly-binary-right-foldable<iter_value_t<I>, I> F>
      requires constructible_from<iter_value_t<I>, iter_reference_t<I>>
    constexpr auto ranges::fold_right_last(I first, S last, F f) ->
      optional<decay_t<invoke_result_t<F&, iter_reference_t<I>, iter_value_t<I>>>>;
    
    template<bidirectional_range R,
             indirectly-binary-right-foldable<range_value_t<R>, iterator_t<R>> F>
     requires constructible_from<range_value_t<R>, range_reference_t<R>>
    constexpr auto ranges::fold_right_last(R&& r, F f) ->
      optional<decay_t<invoke_result_t<F&, range_reference_t<R>, range_value_t<R>>>>;
    

    -4- Let U be decltype(ranges::fold_right(first, last, iter_value_t<I>(*first), f)).

    -5- Effects: Equivalent to:

    if (first == last)
      return optional<U>();
    I tail = ranges::prev(ranges::next(first, std::move(last)));
    return optional<U>(in_place,
      ranges::fold_right(std::move(first), tail, iter_value_t<I>(*tail), std::move(f)));
    
    template<input_iterator I, sentinel_for<I> S, class T = iter_value_t<I>,
             indirectly-binary-left-foldable<T, I> F>
      constexpr see belowauto ranges::fold_left_with_iter(I first, S last, T init, F f) ->
        fold_left_with_iter_result<I, decay_t<invoke_result_t<F&, T, iter_reference_t<I>>>>;
    
    template<input_range R, class T = range_value_t<R>,
             indirectly-binary-left-foldable<T, iterator_t<R>> F>
      constexpr see belowauto ranges::fold_left_with_iter(R&& r, T init, F f) ->
        fold_left_with_iter_result<borrowed_iterator_t<R>,
                                   decay_t<invoke_result_t<F&, T, range_reference_t<R>>>>;
    

    -6- Let U be decay_t<invoke_result_t<F&, T, iter_reference_t<I>>>.

    -7- Effects: Equivalent to:

    if (first == last)
      return {std::move(first), U(std::move(init))};
    U accum = invoke(f, std::move(init), *first);
    for (++first; first != last; ++first)
      accum = invoke(f, std::move(accum), *first);
    return {std::move(first), std::move(accum)};
    

    -8- Remarks: The return type is fold_left_with_iter_result<I, U> for the first overload and fold_left_with_iter_result<borrowed_iterator_t<R>, U> for the second overload.

    template<input_iterator I, sentinel_for<I> S,
             indirectly-binary-left-foldable<iter_value_t<I>, I> F>
      requires constructible_from<iter_value_t<I>, iter_reference_t<I>>
      constexpr see belowauto ranges::fold_left_first_with_iter(I first, S last, F f) ->
        fold_left_first_with_iter_result<
          I, optional<decay_t<invoke_result_t<F&, iter_value_t<I>, iter_reference_t<I>>>>>;
    
    template<input_range R,
             indirectly-binary-left-foldable<range_value_t<R>, iterator_t<R>> F>
      requires constructible_from<range_value_t<R>, range_reference_t<R>>
      constexpr see belowauto ranges::fold_left_first_with_iter(R&& r, F f) ->
        fold_left_first_with_iter_result<
          borrowed_iterator_t<R>,
          optional<decay_t<invoke_result_t<F&, range_value_t<R>, range_reference_t<R>>>>>;
    

    -9- Let U be

    decltype(ranges::fold_left(std::move(first), last, iter_value_t<I>(*first), f))
    

    -10- Effects: Equivalent to:

    if (first == last)
      return {std::move(first), optional<U>()};
    optional<U> init(in_place, *first);
    for (++first; first != last; ++first)
      *init = invoke(f, std::move(*init), *first);
    return {std::move(first), std::move(init)};
    

    -11- Remarks: The return type is fold_left_first_with_iter_result<I, optional<U>> for the first overload and fold_left_first_with_iter_result<borrowed_iterator_t<R>, optional<U>> for the second overload.


4136(i). Specify behavior of [linalg] Hermitian algorithms on diagonal with nonzero imaginary part

Section: 29.9.3 [linalg.general] Status: Tentatively Ready Submitter: Mark Hoemmen Opened: 2024-08-09 Last modified: 2025-10-27

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

Mathematically, the diagonal elements of a Hermitian matrix must all have zero imaginary part (if they are complex numbers). 29.9.3 [linalg.general] paragraphs 3 and 4 govern the behavior of [linalg] functions that operate on "symmetric," "Hermitian," or "triangular" matrices. All such functions only access the specified triangle (lower or upper) of the matrix. Whatever is in the other triangle of the matrix doesn't matter; it's not even accessed. That gives well-defined behavior for "symmetric" and "triangular" matrices. However, both triangles include the diagonal. What should the "Hermitian" functions do if they encounter a diagonal element with nonzero imaginary part?

The current wording says that both the operation performed and the matrix itself are Hermitian, but does not clarify what happens if the latter is not true. For example, 29.9.14.3 [linalg.algs.blas2.hemv] says that hermitian_matrix_vector_product performs a

Hermitian matrix-vector product, taking into account the Triangle parameter that applies to the Hermitian matrix A (29.9.3 [linalg.general]).

Language like this appears in the specifications of all the functions whose names start with hermitian. The implication is that if the diagonal has an element with nonzero imaginary part, then the matrix is not Hermitian and therefore a precondition of the function has been violated. The result is undefined behavior.

We can get rid of this undefined behavior by defining what happens in this case. It turns out that Chapter 2 of the BLAS Standard already does this: It says that the Hermitian algorithms do not access the imaginary parts of diagonal elements. The reference Fortran BLAS implementations of CHEMV (single-precision Complex HErmitian Matrix-Vector product) and ZHEMV (double-precision complex HErmitian Matrix-Vector product) follow the BLAS Standard. CHEMV uses real(A(j,j)) and ZHEMV uses dble(A(j,j)), which means that the BLAS routines only access the real part of each diagonal element.

The clear design intent of P1673R13 was to imitate the behavior of the BLAS Standard and reference BLAS unless otherwise specified. Thus, we propose to specify this behavior in the wording; we do not think this requires LEWG re-review.

In my view, it's fine to retain the existing wording like that in 29.9.14.3 [linalg.algs.blas2.hemv], that refers to a "Hermitian matrix". The matrix defined by the revised [linalg.general] instructions is unambiguously a Hermitian matrix. Just like the "other triangle" of a symmetric or triangular matrix does not affect the behavior of [linalg] symmetric resp. triangular algorithms, the wording fix here will ensure that any imaginary parts of diagonal elements will not affect the behavior of [linalg] Hermitian algorithms.

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

[2025-10-24; Resolves US 170-278]

Proposed resolution:

This wording is relative to N4988.

  1. Modify 29.9.3 [linalg.general] as indicated:

    -4- For any function F that takes a parameter named t, t applies to accesses done through the parameter preceding t in the parameter list of F. Let m be such an access-modified function parameter. F will only access the triangle of m specified by t. For accesses of diagonal elements m[i, i], F will use the value real-if-needed(m[i, i]) if the name of F starts with hermitian. For accesses m[i, j] outside the triangle specified by t, F will use the value […]


4163(i). Can the overload of std::num_get::do_get for bool call the overload for long?

Section: 28.3.4.3.2.3 [facet.num.get.virtuals] Status: Tentatively NAD Submitter: Jiang An Opened: 2024-09-29 Last modified: 2025-02-07

Priority: Not Prioritized

View other active issues in [facet.num.get.virtuals].

View all other issues in [facet.num.get.virtuals].

View all issues with Tentatively NAD status.

Discussion:

28.3.4.3.2.3 [facet.num.get.virtuals]/6 currently says:

Effects: If (str.flags()&ios_base::boolalpha) == 0 then input proceeds as it would for a long except that if a value is being stored into val, […]

It is unclear whether an implementation is allowed to call the overload for long in this case. Currently, libc++'s version calls that overload, while libstdc++ and MSVC STL's don't (example).

As the divergence implementation strategies is observable, perhaps we should clarify on this.

[2025-02-07; Reflector poll: NAD]

I think this is just a libc++ bug. The wording says it "proceeds as it would for long", which is not the same as actually making a virtual call to do_get for long. It can either duplicate the code from do_get for long, or make a non-virtual (i.e. qualified) call to num_get::do_get.

Proposed resolution:


4166(i). concat_view::end() should be more constrained in order to support noncopyable iterators

Section: 25.7.18.2 [range.concat.view] Status: Tentatively Ready Submitter: Yaito Kakeyama & Nana Sakisaka Opened: 2024-10-13 Last modified: 2025-10-23

Priority: Not Prioritized

View other active issues in [range.concat.view].

View all other issues in [range.concat.view].

View all issues with Tentatively Ready status.

Discussion:

There is a case that concat(a, b) compiles but concat(b, a) does not.

auto range_copyable_it = std::vector<int>{1, 2, 3};

std::stringstream ss{"4 5 6"};
auto range_noncopyable_it = std::views::istream<int>(ss);

auto view1 = std::views::concat(range_copyable_it, range_noncopyable_it);
static_assert(std::ranges::range<decltype(view1)>);               // ok
assert(std::ranges::equal(view1, std::vector{1, 2, 3, 4, 5, 6})); // ok

auto view2 = std::views::concat(range_noncopyable_it, range_copyable_it);
// static_assert(std::ranges::range<decltype(view2)>);               // error
// assert(std::ranges::equal(view2, std::vector{4, 5, 6, 1, 2, 3})); // error

The reason behind this is as follows:

Firstly, if all Views... satisfy the std::ranges::range concept, then concat_view should also satisfy it. However, if any of the Views... have a noncopyable iterator and the last view is common_range, the current concat_view fails to model a range.

For concat_view to model a range, its sentinel must satisfy std::semiregular, but concat_view::end() returns a concat_view::iterator, which is noncopyable if the underlying iterator is noncopyable. This issue arises from the proposed implementation where the iterator uses std::variant. Although this specification is exposition-only, even if an alternative type-erasure mechanism is used, copying is still required if the user attempts to copy an iterator.

To resolve the issue, concat_view::end() can and should fallback to returning std::default_sentinel in such cases.

Unfortunately, as a side effect, this fix would prevent concat_view from being a common_range in certain situations. According to P2542R8:

concat_view can be common_range if the last underlying range models common_range

However, this is no longer true after applying our fix. That said, these two issues cannot be resolved simultaneously due to implementability. Therefore, we suggest applying our fix regardless and accepting that concat_view will not always inherit common_range. Note that the current draft (N4988) does not explicitly specify when concat_view can model common_range, so no addition is required for mentioning this point.

A similar issue had been reported as 3385(i), which was eventually adopted as a C++20 DR. This DR indicates that LWG approved the decision to require copyable in order to model a common_iterator.

Previous resolution [SUPERSEDED]:

This wording is relative to N4993.

  1. Modify 25.7.18.2 [range.concat.view] as indicated:

    constexpr auto end() const
      requires (range<const Views> && ...) && concatable<const Views...>;
    

    -7- Effects: Let is-const be true for the const-qualified overload, and false otherwise. Equivalent to:

    constexpr auto N = sizeof...(Views);
    if constexpr ((semiregular<iterator_t<maybe-const<is-const, Views>>> && ...) && 
                  common_range<maybe-const<is-const, Views...[N - 1]>>) {
      return iterator<is-const>(this, in_place_index<N - 1>,
                                ranges::end(std::get<N - 1>(views_)));
    } else {
      return default_sentinel;
    }
    

[2025-03-05; Hewill Kang provides improved wording]

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5001.

  1. Modify 25.7.18.2 [range.concat.view] as indicated:

    constexpr auto end() const
      requires (range<const Views> && ...) && concatable<const Views...>;
    

    -7- Effects: Let is-const be true for the const-qualified overload, and false otherwise. Equivalent to:

    constexpr auto N = sizeof...(Views);
    if constexpr (all-forward<is-const, Views...> && 
                  common_range<maybe-const<is-const, Views...[N - 1]>>) {
      return iterator<is-const>(this, in_place_index<N - 1>,
                                ranges::end(std::get<N - 1>(views_)));
    } else {
      return default_sentinel;
    }
    

4171(i). P2609R3 breaks code that uses views::zip and get<T>

Section: 24.3.6.3 [indirectcallable.indirectinvocable] Status: Tentatively NAD Submitter: S. B. Tam Opened: 2024-11-01 Last modified: 2025-10-23

Priority: Not Prioritized

View all issues with Tentatively NAD status.

Discussion:

The following use of std::ranges::for_each is valid before P2609R3 and invalid after that.

#include <algorithm>
#include <ranges>
#include <tuple>
using namespace std::ranges;

void f() {
  int a[1];
  auto fun = [](auto t) {
    [[maybe_unused]] auto x = std::get<int&>(t);
  };
  for_each(views::zip(a), fun);
}

The reason is that, P2609R3 requires fun to be invocable with iter_value_t<I>&, which is tuple<int>& when I is zip_view's iterator, and tuple<int>& doesn't support std::get<int&>(t) because there isn't a int& member.

P2609R3 argues that "The actual consequence on user code seems small", but I believe that this code pattern is common enough, and it hurts if we cannot use get<int&>(t) in the lambda body.

Note that for_each doesn't actually call fun with iter_value_t<I>, as can be seen by adding an explicit return type to fun.

Did LWG foresee this impact of P2609R3? Could P2609R3 be reverted to unbreak this code pattern?

[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]

The range concepts are over-constrained by design, and indirect_unary_invocable always required invocability with iter_value_t. The P2609 changes enforced this requirement properly for iterators returning proxy references, including zip_iterator.

Proposed resolution:


4184(i). Domain of ranges::cmeow doesn't match ranges::meow

Section: 25.3 [range.access] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2024-12-17 Last modified: 2025-02-07

Priority: Not Prioritized

View all issues with Tentatively NAD status.

Discussion:

ranges::begin/rbegin/data can be used on non-ranges as long as the object has a begin/rbegin/data member, this is also true for their const versions before C++23.

However, in C++23 the const version always applied possibly-const-range to the object, which no longer worked for non-ranges due to this function requiring input_range, which seems to be a breaking change (demo):

#include <ranges>

struct NotRange {
        int* begin();
  const int* begin() const;
        int* rbegin();
  const int* rbegin() const;
        int* data();
  const int* data() const;
};

int main() {
  NotRange r;

  (void) std::ranges::begin(r);
  (void) std::ranges::rbegin(r);
  (void) std::ranges::data(r);

  // The following works in C++20, fails in C++23
  (void) std::ranges::cbegin(r);
  (void) std::ranges::crbegin(r);
  (void) std::ranges::cdata(r);
}

[2025-02-07; Reflector poll: NAD]

"We don't need to support ranges::cbegin on non-ranges."

"Seems to be very similar to LWG 3913(i) which LWG closed as NAD."

Proposed resolution:


4194(i). atomic<void*> should use generic class template

Section: 32.5.8.5 [atomics.types.pointer] Status: Tentatively NAD Submitter: Gonzalo Brito Opened: 2025-01-16 Last modified: 2025-02-07

Priority: Not Prioritized

View other active issues in [atomics.types.pointer].

View all other issues in [atomics.types.pointer].

View all issues with Tentatively NAD status.

Discussion:

32.5.8.5 [atomics.types.pointer] p1 states (emphasis mine):

There is a partial specialization of the atomic class template for pointers.

which requires atomic<void*> to use the atomic class template for pointers. However, the fetch_add/_sub member functions add a difference_type to a T* which requires a pointer-to-object type (these member functions are constexpr, so trying to support this seems unimplementable).

For atomic_ref, the 32.5.7.5 [atomics.ref.pointer] p1 states (emphasis mine):

There are specializations of the atomic_ref` class template for all pointer-to-object types.

which avoids this issue and applying the same form to 32.5.8.5 [atomics.types.pointer] would make atomic<void*> and atomic_ref<void*> consistent.

Technically this would be a breaking change, but all C++ standard library implementations surveyed are broken, and the proposed fix would make them compliant: see libstdc++, libc++ and MSVC STL errors here. These standard libraries require a pointer-to-object type, atomic<void*> uses the general template. Therefore, no user code seems to be impacted.

[2025-02-07; Reflector poll: NAD]

The fetch_OP members have "Mandates: T is a complete object type." and a note explaining that this means arithmetic on void* is ill-formed. So implementations are expected to use the partial specialization for void* but to reject attempts at arithmetic. They all do this correctly today.

Proposed resolution:

This wording is relative to N5001.

  1. Modify 32.5.8.5 [atomics.types.pointer] as indicated:

    -1- There is a partial specialization of the atomic class template for pointerspointer-to-object types. Specializations of this partial specialization are standard-layout structs. They each have a trivial destructor.


4219(i). std::vector::erase[_if] should be based on ranges remove

Section: 23.3.13.6 [vector.erasure] Status: Tentatively NAD Submitter: Peter Kasting Opened: 2025-03-05 Last modified: 2025-10-21

Priority: Not Prioritized

View all issues with Tentatively NAD status.

Discussion:

C++20 added std::vector::erase[_if]. Per 23.3.13.6 [vector.erasure], these are equivalent to a call to std::remove[_if] followed by an appropriate erase.

This is unfortunate, because std::remove_if is specified (by 26.7.8 [alg.remove]) as invoking its predicate as pred(*i), while std::ranges::remove_if uses the more flexible invoke(pred, invoke(proj, *i)). Disregarding the projection, the latter allows the use of member function pointers as predicates, while the former does not.

I assume the committee intentionally did not change the non-ranges version to use invoke because it caused a backwards-compatibility risk. (If I am mistaken and this was an oversight, perhaps this and other non-ranges algorithms that take predicates should be updated to use invoke() to invoke them.)

If that's true, though, it's perplexing why a new-to-c++20 function like std::vector::erase_if should suffer the same drawback.

[2025-10-21; Reflector poll. Status changed: New → Tentatively NAD.]

"This is a design change and needs a paper."

[2025-10-21; .]

"Paper should discuss whether std::erase_if(c, &T::mf) works on paper, currently works on STL implementations in the field, and whether it should work."

Proposed resolution:

This wording is relative to N5001.

  1. Modify 23.3.13.6 [vector.erasure] as indicated:

    template<class T, class Allocator, class U = T>
      constexpr typename vector<T, Allocator>::size_type
        erase(vector<T, Allocator>& c, const U& value);
    

    -1- Effects: Equivalent to:

    auto rit = ranges::remove(c.begin(), c.end(), value);
    auto r = distance(it, c.end());
    c.erase(r.begin()it, rc.end());
    return r.size();
    
    template<class T, class Allocator, class Predicate>
      constexpr typename vector<T, Allocator>::size_type
        erase_if(vector<T, Allocator>& c, Predicate pred);
    

    -2- Effects: Equivalent to:

    auto rit = ranges::remove_if(c.begin(), c.end(), pred);
    auto r = distance(it, c.end());
    c.erase(r.begin()it, rc.end());
    return r.size();
    

4228(i). Does vector<bool, Allocator> mandate that Allocator::value_type is bool?

Section: 23.3.14.1 [vector.bool.pspc] Status: Tentatively NAD Submitter: Stephan T. Lavavej Opened: 2025-03-18 Last modified: 2025-06-13

Priority: Not Prioritized

View all other issues in [vector.bool.pspc].

View all issues with Tentatively NAD status.

Discussion:

N5008 23.3.14.1 [vector.bool.pspc]/2 says:

Unless described below, all operations have the same requirements and semantics as the primary vector template, except that operations dealing with the bool value type map to bit values in the container storage and allocator_traits::construct (20.2.9.3 [allocator.traits.members]) is not used to construct these values.

23.2.2.5 [container.alloc.reqmts]/5 says:

Mandates: allocator_type::value_type is the same as X::value_type.

Is vector<bool, allocator<int>> forbidden? There's implementation divergence: MSVC's STL enforces the mandate, while libc++ and libstdc++ accept this code, discovered while running libc++'s tests with MSVC's STL.

Preferred resolution: I would be satisfied with resolving this as NAD, with a record that LWG believes that "all operations have the same requirements" means that the Mandate applies. I suppose that an editorial note could also be added to this effect, although I don't believe it's necessary.

Alternate resolution: If LWG believes that the Mandate does not apply, and that vector<bool> should be unique among containers in accepting allocator<Anything>, then I believe that a normative sentence should be added to 23.3.14.1 [vector.bool.pspc]/2, specifically creating an exemption to 23.2.2.5 [container.alloc.reqmts]/5.

[2025-06-13; Reflector poll]

Set status to Tentatively NAD. This is just a bug in some implementations (now fixed in libstdc++).

Proposed resolution:


4229(i). std::ranges::to with union return type

Section: 25.5.7.2 [range.utility.conv.to], 25.5.7.3 [range.utility.conv.adaptors] Status: Tentatively NAD Submitter: Jiang An Opened: 2025-03-20 Last modified: 2025-10-20

Priority: Not Prioritized

View other active issues in [range.utility.conv.to].

View all other issues in [range.utility.conv.to].

View all issues with Tentatively NAD status.

Discussion:

LWG 3847(i) made std::ranges::to require the return type (or the target type for the overload returning range adaptor closure object) to be a cv-unqualified class type. Although the term "class type" in core language specification also covers union types, implementations (libstdc++ and MSVC STL) tend to implement this part of the Mandates only with std::is_class_v, which rejects union types.

E.g. the following program is rejected by libstdc++ and MSVC STL (https://godbolt.org/z/MnsY4Tzen):

#include <memory>
#include <ranges>
#include <type_traits>
#include <utility>
#include <vector>

template<class T, class A = std::allocator<T>>
union weird_vector {
  std::vector<T, A> vec_;

  constexpr weird_vector() : vec_() {}
  constexpr weird_vector(const weird_vector& other) : vec_(other.vec_) {}
  constexpr weird_vector(weird_vector&& other) noexcept : vec_(std::move(other.vec_)) {}

  template<class U>
    requires (!std::same_as<std::remove_cvref_t<U>, weird_vector>) &&
      (!std::same_as<std::remove_cvref_t<U>, std::vector<T, A>>) &&
      requires(U&& u) { std::vector<T, A>(std::forward<U>(u)); }
  constexpr explicit weird_vector(U&& u) : vec_(std::forward<U>(u)) {}

  template<class T1, class T2, class... Ts>
    requires requires(T1&& t1, T2&& t2, Ts&&... ts) {
      std::vector<T, A>(std::forward<T1>(t1), std::forward<T2>(t2), std::forward<Ts>(ts)...);
    }
  constexpr weird_vector(T1&& t1, T2&& t2, Ts&&... ts)
    : vec_(std::forward<T1>(t1), std::forward<T2>(t2), std::forward<Ts>(ts)...) {}

  constexpr weird_vector& operator=(const weird_vector& other) {
    vec_ = other.vec_;
    return *this;
  }
  constexpr weird_vector& operator=(weird_vector&& other)
    noexcept(std::is_nothrow_move_assignable_v<std::vector<T, A>>) {
    vec_ = std::move(other.vec_);
    return *this;
  }

  constexpr ~weird_vector() {
    vec_.~vector();
  }
};

int main() {
  int arr[]{42, 1729};
  auto v [[maybe_unused]] = std::ranges::to<weird_vector<int>>(arr);
}

Although libc++ currently accepts this example, the acceptance seems to be a bug, because libc++ hasn't implemented the "class" part in the Mandates at all (llvm/llvm-project#132133).

It's unclear whether union types were intended to be accepted. Perhaps we should follow implementations' choices and reject them.

[2025-10-20; Reflector poll; Status changed: New → Tentatively NAD.]

"Those implementations have bugs and should be fixed."

There's no intrinsic reason why unions (with suitable constructors) should be rejected here."

Proposed resolution:

This wording is relative to N5008.

  1. Modify 25.5.7.2 [range.utility.conv.to] as indicated:

    template<class C, input_range R, class... Args> requires (!view<C>)
      constexpr C to(R&& r, Args&&... args);
    

    -1- Mandates: C is a cv-unqualified non-union class type.

    […]

  2. Modify 25.5.7.3 [range.utility.conv.adaptors] as indicated:

    template<class C, class... Args> requires (!view<C>)
      constexpr auto to(Args&&... args);
    template<template<class...> class C, class... Args>
      constexpr auto to(Args&&... args);
    

    -1- Mandates: For the first overload, C is a cv-unqualified non-union class type.

    […]


4244(i). Whether the spuriously failed comparison applies to compare_exchange_strong is unclear

Section: 32.5.7.2 [atomics.ref.ops], 32.5.8.2 [atomics.types.operations] Status: Tentatively NAD Submitter: jim x Opened: 2025-04-17 Last modified: 2025-10-20

Priority: Not Prioritized

View other active issues in [atomics.ref.ops].

View all other issues in [atomics.ref.ops].

View all issues with Tentatively NAD status.

Discussion:

Both compare_exchange_strong and compare_exchange_weak share the same specified rule

If and only if the comparison is false then, after the atomic operation, the value in expected is replaced by the value read from the value referenced by *ptr during the atomic comparison.

However, there is a remark for the weak version

A weak compare-and-exchange operation may fail spuriously.

That is, even when the contents of memory referred to by expected and ptr are equal, it may return false and store back to expected the same memory contents that were originally there.

However, we don't explicitly say whether the strong version can have the spuriously failed comparison. The status quo is that we can only infer the point from the name, namely, the strong version should have a stronger guarantee than the weak version.

Suggested resolution:

Explicitly specify whether compare_exchange_strong can have the spurious failed comparison.

[2025-10-20; Reflector poll. Status changed: New → Tentatively NAD.]

"The Effects: explain the behaviour, and don't allow for spurious failures."

"The Remarks: are normative and are explicitly adding permission to fail spuriously. Absent such permission, the specification for strong operations is clear and has no spurious failures."

Proposed resolution:


4246(i). Redundant constraint in range_formatter::format

Section: 28.5.7.2 [format.range.formatter] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2025-04-18 Last modified: 2025-06-12

Priority: Not Prioritized

View all other issues in [format.range.formatter].

View all issues with Tentatively NAD status.

Discussion:

Currently, the signature of range_formatter::format is as follows:

template<ranges::input_range R, class FormatContext>
  requires formattable<ranges::range_reference_t<R>, charT> &&
           same_as<remove_cvref_t<ranges::range_reference_t<R>>, T>
typename FormatContext::iterator
  format(R&& r, FormatContext& ctx) const;

which requires that the reference type of the range parameter must be formattable, and such type must be exactly T after removing the cvref-qualifiers.

However, satisfying the latter always implies satisfying the former, as the range_formatter class already requires that T must be formattable.

There is no need to perform a redundant check here.

[2025-06-12; Reflector poll]

Set status to Tentatively NAD. This is not redundant, it might check that const T is formattable, which is not the same as checking that T is formattable.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 28.5.7.2 [format.range.formatter] as indicated:

    namespace std {
      template<class T, class charT = char>
        requires same_as<remove_cvref_t<T>, T> && formattable<T, charT>
      class range_formatter {
        […]
        template<ranges::input_range R, class FormatContext>
            requires formattable<ranges::range_reference_t<R>, charT> &&
                     same_as<remove_cvref_t<ranges::range_reference_t<R>>, T>
          typename FormatContext::iterator
            format(R&& r, FormatContext& ctx) const;
      };
    }
    
    […]
    template<ranges::input_range R, class FormatContext>
      requires formattable<ranges::range_reference_t<R>, charT> &&
               same_as<remove_cvref_t<ranges::range_reference_t<R>>, T>
    typename FormatContext::iterator
      format(R&& r, FormatContext& ctx) const;
    

    -11- Effects: Writes the following into ctx.out(), adjusted according to the range-format-spec:


4253(i). basic_const_iterator should provide iterator_type

Section: 24.5.3.3 [const.iterators.iterator] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2025-04-29 Last modified: 2025-06-13

Priority: Not Prioritized

View other active issues in [const.iterators.iterator].

View all other issues in [const.iterators.iterator].

View all issues with Tentatively Ready status.

Discussion:

Currently, iterator adaptors in <iterator> that wrap a single iterator such as reverse_iterator, move_iterator, and counted_iterator all provide a public iterator_type member for users to access the underlying iterator type, except for basic_const_iterator (demo):

#include <iterator>

using I  = int*;
using RI = std::reverse_iterator<I>;
using MI = std::move_iterator<I>;
using CI = std::counted_iterator<I>;
using BI = std::basic_const_iterator<I>;

static_assert(std::same_as<RI::iterator_type, I>);
static_assert(std::same_as<MI::iterator_type, I>);
static_assert(std::same_as<CI::iterator_type, I>);
static_assert(std::same_as<BI::iterator_type, I>); // error

It seems reasonable to add one for basic_const_iterator for consistency.

[2025-06-12; Reflector poll]

Set status to Tentatively Ready after eight votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 24.5.3.3 [const.iterators.iterator] as indicated:

    namespace std {
      […]
      template<input_iterator Iterator>
      class basic_const_iterator {
        Iterator current_ = Iterator();                             // exposition only
        using reference = iter_const_reference_t<Iterator>;         // exposition only
        using rvalue-reference =                                    // exposition only
          iter-const-rvalue-reference-t<Iterator>;
              
        public:
          using iterator_type = Iterator;
          using iterator_concept = see below;
          using iterator_category = see below;  // not always present
          using value_type = iter_value_t<Iterator>;
          using difference_type = iter_difference_t<Iterator>;
          […]
      };
    }
    

4255(i). move_only_function constructor should recognize empty copyable_functions

Section: 22.10.17.4.3 [func.wrap.move.ctor] Status: Tentatively Ready Submitter: Tomasz Kamiński Opened: 2025-05-12 Last modified: 2025-10-22

Priority: Not Prioritized

View other active issues in [func.wrap.move.ctor].

View all other issues in [func.wrap.move.ctor].

View all issues with Tentatively Ready status.

Discussion:

The standard currently requires that constructing move_only_function from empty copyable_function, creates an non-empty move_only_function, that contains an empty copyable_function as the target. For example:

std::copyable_function<int(int)> ce;
std::move_only_function<int(int)> me(ce);

We require that invoking me(1) is undefined behavior (as it leads to call to the ce(1)), however it cannot be detected in the user code, as me != nullptr is true.

We should require the move_only_function(F&& f) constructor to create an empty object, if f is an instantiation of copyable_function and f == nullptr is true, i.e. f does not contain target object.

This simplifies implementing avoidance of double wrapping per 22.10.17.1 [func.wrap.general] p2, as transferring the target produces an empty functor.

The copyable_function cannot be constructed from move_only_function, as it requires functor to be copyable. Invoking an empty std::function has well defined behavior (throws bad_function_call), and wrapping such object into other functors should reproduce that behavior.

[2025-10-22; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 22.10.17.4.3 [func.wrap.move.ctor] as indicated:

    template<class F> move_only_function(F&& f);
    
    […]

    -8- Postconditions:: *this has no target object if any of the following hold:

    1. (8.1) — f is a null function pointer value, or

    2. (8.2) — f is a null member pointer value, or

    3. (8.2) — remove_cvref_t<F> is a specialization of the move_only_function or copyable_function class template, and f has no target object.


4256(i). Incorrect constrains for function_ref constructors from nontype_t

Section: 22.10.17.6.3 [func.wrap.ref.ctor] Status: Tentatively Ready Submitter: Tomasz Kamiński Opened: 2025-05-14 Last modified: 2025-10-21

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

For the following class:

struct M
{
   void operator()();
};

The constructor of function_ref<void()> from nontype_t is considered to be valid candidate (is_constructible_v<function_ref<void()>, nontype_t<M{}>> is true), despite the fact that the corresponding invocation of template argument object, that is const lvalue, is ill-formed. As consequence we produce a hard error from inside of this constructor.

This is caused by the fact that for constructors with non-type auto f parameter, we are checking if is-invocable-using<F> is true, where F is decltype(f) i.e. M for the example. We should use const F& or decltype((f)).

[2025-10-21; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 22.10.17.6.3 [func.wrap.ref.ctor] as indicated:

    template<auto f> constexpr function_ref(nontype_t<f>) noexcept;
    

    -8- Let F be decltype(f).

    -9- Constraints: is-invocable-using<const F&> is true.

    […]
    template<auto f, class U> constexpr function_ref(nontype_t<f>, U&& obj) noexcept;
    

    -12- Let T be remove_reference_t<U> and F be decltype(f).

    -13- Constraints::

    1. (13.1) — is_rvalue_reference_v<U&&> is false, and

    2. (13.2) — is-invocable-using<const F&, T cv&> is true.

    […]
    template<auto f, class T> constexpr function_ref(nontype_t<f>, T cv* obj) noexcept;
    

    -17- Let F be decltype(f).

    -16- Constraints: is-invocable-using<const F&, T cv*> is true.

    […]

4257(i). Stream insertion for chrono::local_time should be constrained

Section: 30.7.9 [time.clock.local] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-05-16 Last modified: 2025-08-26

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

Stream insertion for chrono::local_time is defined in terms of conversion to chrono::sys_time, but not all chrono::sys_time specializations can be inserted into an ostream, because one of the overloads is constrained and the other requires convertibility to chrono::sys_days (see 30.7.2.3 [time.clock.system.nonmembers]).

This means the following code fails to compile:


#include <iostream>
#include <chrono>

template<typename T>
concept ostream_insertable = requires (std::ostream& o, const T& t) { o << t; };

using D = std::chrono::duration<double>;

int main() {
  if constexpr (ostream_insertable<std::chrono::sys_time<D>>)
    std::cout << std::chrono::sys_time<D>{};
  if constexpr (ostream_insertable<std::chrono::local_time<D>>)
    std::cout << std::chrono::local_time<D>{}; // FAIL
}
The first condition is false, because there's no overload that's suitable. The second is true, because the operator<< overload for chrono::local_time isn't constrained and so insertion appears to be valid. But actually trying to use it is ill-formed, because it tries to convert the local_time<D> to a sys_time<D> and then insert that, which isn't valid.

[2025-08-21; Reflector poll]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 30.7.9 [time.clock.local] as indicated:

    
    template<class charT, class traits, class Duration>
      basic_ostream<charT, traits>&
        operator<<(basic_ostream<charT, traits>& os, const local_time<Duration>& lt);
    

    -?- Constraints: os << sys_time<Duration>{lt.time_since_epoch()} is a valid expression.

    -2- Effects:

     os << sys_time<Duration>{lt.time_since_epoch()};
    

    -3- Returns: os.


4265(i). std::midpoint should not accept const bool

Section: 26.10.16 [numeric.ops.midpoint] Status: Tentatively Ready Submitter: Jan Schultke Opened: 2025-05-21 Last modified: 2025-10-21

Priority: Not Prioritized

View all other issues in [numeric.ops.midpoint].

View all issues with Tentatively Ready status.

Discussion:

The constraints of the first overload of std::midpoint are as follows:

template<class T>
  constexpr T midpoint(T a, T b) noexcept;

-1- Constraints: T is an arithmetic type other than bool.

It does not appear intentional that const bool is supported considering that 26.10.14 [numeric.ops.gcd] excludes cv bool.

More generally, it is questionable that cv-qualified arithmetic types aren't excluded in general, considering that 26.10.17 [numeric.sat] excludes them with the "signed or unsigned integer type" constraint. However, tightening the constraints for these other functions seems evolutionary, not like fixing an obvious oversight, and so it likely requires a separate issue.

[2025-10-21; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

This requires template argument to be explicty specified, i.e. midpoint<const bool>. I would prefer to address all cv-qualified types at once, e.g. Constraints: remove_cv_t<T> is an arithmetic type other than bool."

"This is locally consistent with gcd and lcm which only exclude cv bool. [algorithms.requirement] p15 makes it unspecified to use an explicit template argument list here, so midpoint<const bool> and midpoint<const int> are already unspecified, this issue just ensures that const bool is explicitly rejected, like bool."

Proposed resolution:

This wording is relative to N5008.

  1. Modify 26.10.16 [numeric.ops.midpoint] as indicated:

    template<class T>
      constexpr T midpoint(T a, T b) noexcept;
    

    -1- Constraints: T is an arithmetic type other than cv bool.


4266(i). layout_stride::mapping should treat empty mappings as exhaustive

Section: 23.7.3.4.7 [mdspan.layout.stride] Status: Tentatively Ready Submitter: Tomasz Kamiński Opened: 2025-05-22 Last modified: 2025-06-13

Priority: Not Prioritized

View all other issues in [mdspan.layout.stride].

View all issues with Tentatively Ready status.

Discussion:

Mapping over an empty multidimensional index space is always exhaustive according to the corresponding definitions from 23.7.3.4.2 [mdspan.layout.reqmts] p16.

However, the current specification of layout_stride::mapping does not consider whether some of the empty multidimensional index spaces are unique or exhaustive. For illustration, the mapping with the following configuration is not considered exhaustive according to the current specification of 23.7.3.4.7.4 [mdspan.layout.stride.obs] bullet 5.2:

extents: 2, 2, 0
strides: 2, 6, 20

This prevents the implementation from implementing sm.is_exhaustive() as sm.fwd-prod-of-extents(sm::extents_type::rank()) == sm.required_span_size(). For all mappings with size greater than zero, such an expression provides an answer consistent with the standard. However, it always returns true for an empty mapping, such as shown in the example.

We should make such implementation conforming, and require is_exhaustive() to return true for empty mappings.

For consistency, we could update is_always_exhaustive() to recognize mapping with rank() == 0, and one for which at least one of the static extents is equal to zero (i.e., they always represent a multidimensional index space).

[2025-06-12; Reflector poll]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 23.7.3.4.7.1 [mdspan.layout.stride.overview] as indicated:

    namespace std {
      template<class Extents>
      class layout_stride::mapping {
        […]
        static constexpr bool is_always_unique() noexcept { return true; }
        static constexpr bool is_always_exhaustive() noexcept; { return false; }
        static constexpr bool is_always_strided() noexcept { return true; }
        […]
      };
    }
    
  2. Modify 23.7.3.4.7.4 [mdspan.layout.stride.obs] as indicated:

    […]

    static constexpr bool is_always_exhaustive() noexcept;
    

    -?- Returns: true if rank_ is 0 or if there is a rank index r of extents() such that extents_type::static_extent(r) is 0, otherwise false.

    constexpr bool is_exhaustive() const noexcept;
    

    -5- Returns:

    1. (5.1) — true if rank_ or the size of the multidimensional index space m.extents() is 0.

    2. (5.2) — […]

    3. (5.3) — […]


4269(i). unique_copy passes arguments to its predicate backwards

Section: 26.7.9 [alg.unique] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-05-29 Last modified: 2025-10-17

Priority: Not Prioritized

View other active issues in [alg.unique].

View all other issues in [alg.unique].

View all issues with Tentatively Ready status.

Discussion:

For the unique algorithms, 26.7.9 [alg.unique] p1 says:

1. Let pred be equal_to{} for the overloads with no parameter pred, and let E be
  1. (1.1) — bool(pred(*(i - 1), *i)) for the overloads in namespace std;
  2. (1.2) — bool(invoke(comp, invoke(proj, *(i - 1)), invoke(proj, *i))) for the overloads in namespace ranges.

However for the unique_copy algorithms, 26.7.9 [alg.unique] p6 says that the arguments *i and *(i-1) should be reversed:

6. Let pred be equal_to{} for the overloads with no parameter pred, and let E be
  1. (6.1) — bool(pred(*i, *(i - 1))) for the overloads in namespace std;
  2. (6.2) — bool(invoke(comp, invoke(proj, *i), invoke(proj, *(i - 1)))) for the overloads in namespace ranges.

This reversed order is consistent with the documentation for SGI STL unique_copy, although the docs for SGI STL unique show reversed arguments too, and the C++ standard doesn't match that.

A survey of known implementations shows that all three of libstdc++, libc++, and MSVC STL use the pred(*(i - 1), *i) order for all of std::unique, std::unique_copy, ranges::unique, and ranges::unique_copy. The range-v3 library did the same, and even the SGI STL did too (despite what its docs said). Only two implementations were found which match the spec and use a different argument order for unique and unique_copy, Casey Carter's (cmcstl2) and Fraser Gordon's.

In the absence of any known rationale for unique and unique_copy to differ, it seems sensible to make unique_copy more consistent with unique (and with the majority of implementations stretching back three decades).

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Fixed misplaced ) in the (6.1) change as pointed out on reflector, and rebased on N5014.

"I remain inconvinced that this actually matters given the equivalence relation requirement."

Proposed resolution:

This wording is relative to N5014.

  1. Modify 26.7.9 [alg.unique] as indicated:

    6. Let pred be equal_to{} for the overloads with no parameter pred, and let E(i) be
    1. (6.1) — bool(pred(*i, *(i - 1), *i)) for the overloads in namespace std;
    2. (6.2) — bool(invoke(comp, invoke(proj, *i), invoke(proj, *(i - 1)), invoke(proj, *i))) for the overloads in namespace ranges.

4271(i). Caching range views claim amortized amortized 𝒪(1) runtime complexity for algorithms that are in fact 𝒪(n)

Section: 24.3.1 [iterator.requirements.general], 25.4.3 [range.approximately.sized], 25.4.1 [range.req.general], 25.4.2 [range.range], 25.4.4 [range.sized], 25.7.8.2 [range.filter.view], 25.7.12.2 [range.drop.view], 25.7.13.2 [range.drop.while.view], 25.7.17.2 [range.split.view], 25.7.21.2 [range.reverse.view], 25.7.30.2 [range.slide.view], 25.7.31.2 [range.chunk.by.view] Status: Tentatively NAD Submitter: Andreas Weis Opened: 2025-06-02 Last modified: 2025-10-23

Priority: Not Prioritized

View all other issues in [iterator.requirements.general].

View all issues with Tentatively NAD status.

Discussion:

Currently range views that cache the result of their operation claim an amortized 𝒪(1) worst-case runtime complexity. This is inconsistent with the established practice in algorithm analysis, where the given complexity bound must hold for all possible sequences of operations. Caching is not sufficient to lower the complexity bound here, as the sequence that contains only a single call to the operation will cause a runtime cost linear in the size of the underlying range. Thus all of the caching range operations are in fact 𝒪(n).

Apart from the caching view operations, this also has secondary impacts in other places that rely on the complexity of iterator functions, such as the iterator requirements and functions for computing the size of a range.

It is unclear how desirable it is under these circumstances to continue disallowing other kinds of 𝒪(n) behavior for iterator functions. While caching offers clear benefits in the context of lazy evaluation, it cannot prevent losing the 𝒪(1) complexity guarantee. The proposed changes below therefore do not address the issue that other types of views (such as hypothetical non-caching variants of the affected views) that were previously considered invalid will become valid with these changes.

[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]

Relaxing complexity of ranges::begin/ranges::end is design change, and not direction we want to pursue.

The "amortized constant" are not quite right words to describe intended requirements. A rework preserving the intent of current wording would be welcomed.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 24.3.1 [iterator.requirements.general] as indicated:

    -14- All the categories of iterators require only those functions that are realizable for a given category in constant time (amortized)linear time. Therefore, requirement tables and concept definitions for the iterators do not specify complexity.

  2. Modify 25.4.3 [range.approximately.sized] as indicated:

    -1- The approximately_sized_range concept refines range with the requirement that an approximation of the number of elements in the range can be determined in amortized constantlinear time using ranges::reserve_hint.

  3. Modify 25.4.1 [range.req.general] as indicated:

    -2- The range concept requires that ranges::begin and ranges::end return an iterator and a sentinel, respectively. The sized_range concept refines range with the requirement that ranges::size be amortized 𝒪(1n). The view concept specifies requirements on a range type to provide operations with predictable complexity.

  4. Modify 25.4.2 [range.range] as indicated:

    -2- Given an expression t such that decltype((t)) is T&, T models range only if

    1. (2.1) — […]

    2. (2.2) — both ranges::begin(t) and ranges::end(t) are amortized constantlinear time and non-modifying, and

    3. (2.3) — […]

  5. Modify 25.4.4 [range.sized] as indicated:

    -1- The sized_range concept refines approximately_sized_range with the requirement that the number of elements in the range can be determined in amortized constantlinear time using ranges::size.

    template<class T>
      concept sized_range =
        approximately_sized_range<T> && requires(T& t) { ranges::size(t); };
    

    -2- Given an lvalue t of type remove_reference_t<T>, T models sized_range only if

    1. (2.1) — ranges::size(t) is amortized 𝒪(1n), does not modify t, and is equal to ranges::distance(ranges::begin(t), ranges::end(t)), and

    2. (2.2) — […]

  6. Modify 25.7.8.2 [range.filter.view] as indicated:

    constexpr iterator begin();
    

    -3- Preconditions: […]

    -4- Returns: […]

    -5- Remarks: In order to provide the amortized constant time complexity required by the range concept when filter_view models forward_range, thisThis function caches the result within the filter_view for use on subsequent calls.

  7. Modify 25.7.12.2 [range.drop.view] as indicated:

    constexpr auto begin()
      requires (!(simple-view<V> &&
                  random_access_range<const V> && sized_range<const V>));
    constexpr auto begin() const
      requires random_access_range<const V> && sized_range<const V>;
    

    -3- Returns: […]

    -4- Remarks: In order to provide the amortized constant-time complexity required by the range concept when drop_view models forward_range, theThe first overload caches the result within the drop_view for use on subsequent calls.

  8. Modify 25.7.13.2 [range.drop.while.view] as indicated:

    constexpr auto begin();
    

    -3- Preconditions: […]

    -4- Returns: […]

    -5- Remarks: In order to provide the amortized constant-time complexity required by the range concept when drop_while_view models forward_range, theThe first call caches the result within the drop_while_view for use on subsequent calls.

  9. Modify 25.7.17.2 [range.split.view] as indicated:

    constexpr iterator begin();
    

    -3- Returns: […]

    -4- Remarks: In order to provide the amortized constant time complexity required by the range concept, thisThis function caches the result within the split_view for use on subsequent calls.

  10. Modify 25.7.21.2 [range.reverse.view] as indicated:

    constexpr reverse_iterator<iterator_t<V>> begin();
    

    -2- Returns: […]

    -3- Remarks: In order to provide the amortized constant time complexity required by the range concept, thisThis function caches the result within the reverse_view for use on subsequent calls.

  11. Modify 25.7.30.2 [range.slide.view] as indicated:

    constexpr auto begin()
      requires (!(simple-view<V> && slide-caches-nothing<const V>));
    

    -3- Returns: […]

    -4- Remarks: In order to provide the amortized constant-time complexity required by the range concept, thisThis function caches the result within the slide_view for use on subsequent calls when V models slide-caches-first.

    […]
    constexpr auto end()
      requires (!(simple-view<V> && slide-caches-nothing<const V>));
    

    -6- Returns: […]

    -7- Remarks: In order to provide the amortized constant-time complexity required by the range concept, thisThis function caches the result within the slide_view for use on subsequent calls when V models slide-caches-first.

  12. Modify 25.7.31.2 [range.chunk.by.view] as indicated:

    constexpr iterator begin();
    

    -3- Preconditions: […]

    -4- Returns: […]

    -5- Remarks: In order to provide the amortized constant-time complexity required by the range concept, thisThis function caches the result within the chunk_by_view for use on subsequent calls.


4274(i). The chrono::hh_mm_ss constructor is ill-formed for unsigned durations

Section: 30.9.2 [time.hms.members] Status: Tentatively Ready Submitter: Michael Welsh Duggan Opened: 2025-06-04 Last modified: 2025-06-13

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

In 30.9.2 [time.hms.members], paragraph 3, the current wording for the constructor of hh_mm_ss expresses some of its requirements in terms of abs(d), which is assumed to be chrono::abs(chrono::duration). chrono::abs is not defined, however, for durations with an unsigned representation. I believe that not being able to create hh_mm_ss objects from unsigned durations is unintentional.

Moreover, is_constructible_v<hh_mm_ss<ud>, ud> is required to be true by the standard for any duration, so making it actually work makes a lot of sense.

[2025-06-13; Reflector poll]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 30.9.2 [time.hms.members] as indicated:

    constexpr explicit hh_mm_ss(Duration d);
    

    -3- Effects: Constructs an object of type hh_mm_ss which represents the Duration d with precision precision.

    1. (3.1) — Initializes is_neg with d < Duration::zero(). Let ABS_D represent -d if is_neg is true and d otherwise.

    2. (3.2) — Initializes h with duration_cast<chrono::hours>(abs(d)ABS_D).

    3. (3.3) — Initializes m with duration_cast<chrono::minutes>(abs(d)ABS_D - hours()).

    4. (3.4) — Initializes s with duration_cast<chrono::seconds>(abs(d)ABS_D - hours() - minutes()).

    5. (3.5) — If treat_as_floating_point_v<precision::rep> is true, initializes ss with abs(d)ABS_D - hours() - minutes() - seconds(). Otherwise, initializes ss with duration_cast<precision>(abs(d)ABS_D - hours() - minutes() - seconds()).


4275(i). std::dynamic_extent should also be defined in <mdspan>

Section: 23.7.3.2 [mdspan.syn] Status: Tentatively Ready Submitter: Aiden Grossman Opened: 2025-06-06 Last modified: 2025-10-17

Priority: 3

View all other issues in [mdspan.syn].

View all issues with Tentatively Ready status.

Discussion:

std::dynamic_extent can be used in certain circumstances in std::mdspan, such as with padded layouts. However, std::dynamic_extent is currently only defined in <span> which necessitates including <span> solely for the std::dynamic_extent definition.

Previous resolution [SUPERSEDED]:

This wording is relative to N5008.

  1. Modify 23.7.3.2 [mdspan.syn], header <span> synopsis, as indicated:

    // all freestanding
    namespace std {
      // constants
      inline constexpr size_t dynamic_extent = numeric_limits<size_t>::max();
      
      // 23.7.3.3 [mdspan.extents], class template extents
      template<class IndexType, size_t... Extents>
      class extents;
    
      […]
    }
    

[2025-06-10; Jonathan provides improved wording]

[2025-10-15; Reflector poll]

Set priority to 3 after reflector poll.

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after eight votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 23.7.1 [views.general] as indicated:

    The header <span> (23.7.2.1 [span.syn]) defines the view span. The header <mdspan> (23.7.3.2 [mdspan.syn]) defines the class template mdspan and other facilities for interacting with these multidimensional views.

    -?- In addition to being available via inclusion of the <span> header, dynamic_extent is available when the header <mdspan> is included.


4276(i). front() and back() are not hardened for zero-length std::arrays

Section: 23.3.3.5 [array.zero] Status: Tentatively Ready Submitter: Jan Schultke Opened: 2025-06-08 Last modified: 2025-08-26

Priority: Not Prioritized

View other active issues in [array.zero].

View all other issues in [array.zero].

View all issues with Tentatively Ready status.

Discussion:

The intent of P3471 "Standard library hardening" is clearly to provide hardened preconditions for members of sequence containers, including std::array. However, a zero-length std::array dodges this hardening by having undefined behavior for front() and back() explicitly specified in 23.3.3.5 [array.zero] paragraph 3.

Without this paragraph, front() and back() would be hardened as well, as specified in 23.2.4 [sequence.reqmts].

[2025-08-21; Reflector poll]

Set status to Tentatively Ready after eight votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 23.3.3.5 [array.zero] as indicated:

    -3- The effect of calling front() or back() for a zero-sized array is undefined.


4280(i). simd::partial_load uses undefined identifier T

Section: 29.10.8.7 [simd.loadstore] Status: Tentatively Ready Submitter: Tim Song Opened: 2025-06-21 Last modified: 2025-08-26

Priority: Not Prioritized

View other active issues in [simd.loadstore].

View all other issues in [simd.loadstore].

View all issues with Tentatively Ready status.

Discussion:

The Effects: element of std::simd::partial_load (after the latest rename) uses T but that is not defined anywhere. It should be V::value_type.

Also, this paragraph should be a Returns: element.

[2025-08-21; Reflector poll]

Set status to Tentatively Ready after nine votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 29.10.8.7 [simd.loadstore] as indicated:

    
    template<class V = see below, ranges::contiguous_range R, class... Flags>
      requires ranges::sized_range<R>
      constexpr V partial_load(R&& r, flags<Flags...> f = {});
    […]
    template<class V = see below, contiguous_iterator I, sized_sentinel_for<I> S, class... Flags>
      constexpr V partial_load(I first, S last, const typename V::mask_type& mask,
                               flags<Flags...> f = {});
    

    -6- […]

    -7- Mandates: […]

    -8- Preconditions: […]

    -9- Effects: Initializes theReturns: A basic_simd object whose ith element is initialized with mask[i] && i < ranges::size(r) ? static_cast<T>(ranges::data(r)[i]) : T() for all i in the range of [0, V::size()), where T is V::value_type.

    -10- Remarks: The default argument for template parameter V is basic_simd<ranges::range_value_t<R>>.


4286(i). Some more feature-test macros for fully freestanding features are not marked freestanding

Section: 17.3.2 [version.syn], 20.2.2 [memory.syn] Status: Tentatively Ready Submitter: Yihe Li Opened: 2025-06-17 Last modified: 2025-10-14

Priority: Not Prioritized

View other active issues in [version.syn].

View all other issues in [version.syn].

View all issues with Tentatively Ready status.

Discussion:

P1642R11 (accepted in C++23) plus LWG 4189(i) (accepted in Hagenberg) added nearly the entire <ranges> header to freestanding. However, the only feature-test macro being added to freestanding is __cpp_lib_ranges_cache_latest, which seems weird, since views::enumerate is also added to freestanding following the blanket comment strategy, but its feature-test macro remains not in freestanding. In retrospective, since all range algorithms are in freestanding via P2976, all __cpp_lib_ranges_* FTMs (except __cpp_lib_ranges_generate_random since ranges::generate_random is not in freestanding) should probably be marked as freestanding.

Furthermore, LWG 4126(i) left out some other FTMs for fully freestanding features. They are also added in the following wording.

A note about is_sufficiently_aligned: P2897R7 does indicate in 5.7.6.1 that the function should be freestanding, but somehow the wording didn't say so. The following wording includes the function and its FTM anyway since hopefully this is just an omission when wording the paper.

[2025-10-14; Reflector poll]

Set status to Tentatively Ready after nine votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 17.3.2 [version.syn], header <version> synopsis, as indicated:

    […]
    #define __cpp_lib_aligned_accessor             202411L // freestanding, also in <mdspan>
    […]                                     
    #define __cpp_lib_array_constexpr              201811L // freestanding, also in <iterator>, <array>
    […]                                     
    #define __cpp_lib_clamp                        201603L // freestanding, also in <algorithm>
    […]                                     
    #define __cpp_lib_constexpr_numeric            201911L // freestanding, also in <numeric>
    […]                                     
    #define __cpp_lib_function_ref                 202306L // freestanding, also in <functional>
    #define __cpp_lib_gcd_lcm                      201606L // freestanding, also in <numeric>
    […]
    #define __cpp_lib_integer_comparison_functions 202002L // freestanding, also in <utility>
    […]
    #define __cpp_lib_is_sufficiently_aligned      202411L // freestanding, also in <memory>
    […]
    #define __cpp_lib_ranges_contains              202207L // freestanding, also in <algorithm>
    #define __cpp_lib_ranges_enumerate             202302L // freestanding, also in <ranges>
    #define __cpp_lib_ranges_find_last             202207L // freestanding, also in <algorithm>
    #define __cpp_lib_ranges_fold                  202207L // freestanding, also in <algorithm>
    […]
    #define __cpp_lib_ranges_iota                  202202L // freestanding, also in <numeric>
    […]
    #define __cpp_lib_ranges_starts_ends_with      202106L // freestanding, also in <algorithm>
    […]
    #define __cpp_lib_robust_nonmodifying_seq_ops  201304L // freestanding, also in <algorithm>
    #define __cpp_lib_sample                       201603L // freestanding, also in <algorithm>
    #define __cpp_lib_saturation_arithmetic        202311L // freestanding, also in <numeric>
    […]
    
  2. Modify 20.2.2 [memory.syn], header <memory> synopsis, as indicated:

    […]
    template<size_t Alignment, class T>
      bool is_sufficiently_aligned(T* ptr);      // freestanding
    […]
    

4291(i). explicit map(const Allocator&) should be constexpr

Section: 23.4.3.1 [map.overview] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-07-10 Last modified: 2025-08-26

Priority: Not Prioritized

View other active issues in [map.overview].

View all other issues in [map.overview].

View all issues with Tentatively Ready status.

Discussion:

The intent of P3372R3 was for all container constructors to be constexpr, but during application of the paper to the working draft it was observed that one map constructor was missed.

[2025-08-21; Reflector poll]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 23.4.3.1 [map.overview] as indicated:

    
    // 23.4.3.2, construct/copy/destroy
    constexpr map() : map(Compare()) { }
    constexpr explicit map(const Compare& comp, const Allocator& = Allocator());
    template<class InputIterator>
      constexpr map(InputIterator first, InputIterator last,
                    const Compare& comp = Compare(), const Allocator& = Allocator());
    template<container-compatible-range <value_type> R>
      constexpr map(from_range_t, R&& rg, const Compare& comp = Compare(),
                    const Allocator& = Allocator());
    constexpr map(const map& x);
    constexpr map(map&& x);
    constexpr explicit map(const Allocator&);
    constexpr map(const map&, const type_identity_t<Allocator>&);
    constexpr map(map&&, const type_identity_t<Allocator>&);
    constexpr map(initializer_list<value_type>, const Compare& = Compare(),
                  const Allocator& = Allocator());
    

4292(i). Unordered container local iterators should be constexpr iterators

Section: 23.5.3.1 [unord.map.overview], 23.5.4.1 [unord.multimap.overview], 23.5.6.1 [unord.set.overview], 23.5.7.1 [unord.multiset.overview] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-07-10 Last modified: 2025-08-29

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

The intent of P3372R3 was for all container iterators to be constexpr iterators, but during application of the paper to the working draft it was observed that unordered containers don't say it for their local iterators.

[2025-08-29; Reflector poll]

Set status to Tentatively Ready after eight votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 23.5.3.1 [unord.map.overview] as indicated:

    -4- The types iterator, and const_iterator, local_iterator, and const_local_iterator meet the constexpr iterator requirements (24.3.1 [iterator.requirements.general]).

  2. Modify 23.5.4.1 [unord.multimap.overview] as indicated:

    -4- The types iterator, and const_iterator, local_iterator, and const_local_iterator meet the constexpr iterator requirements (24.3.1 [iterator.requirements.general]).

  3. Modify 23.5.6.1 [unord.set.overview] as indicated:

    -4- The types iterator, and const_iterator, local_iterator, and const_local_iterator meet the constexpr iterator requirements (24.3.1 [iterator.requirements.general]).

  4. Modify 23.5.7.1 [unord.multiset.overview] as indicated:

    -4- The types iterator, and const_iterator, local_iterator, and const_local_iterator meet the constexpr iterator requirements (24.3.1 [iterator.requirements.general]).


4293(i). span::subspan/first/last chooses wrong constructor when T is const-qualified bool

Section: 23.7.2.2.4 [span.sub] Status: Tentatively Ready Submitter: Yuhan Liu Opened: 2025-07-11 Last modified: 2025-08-26

Priority: Not Prioritized

View all other issues in [span.sub].

View all issues with Tentatively Ready status.

Discussion:

In section 23.7.2.2.4 [span.sub], paragraphs p12, p14, and p16 erroneously use the initializer list constructor for span instead of the intended iterator/count constructor.

Specifically, in these paragraphs, the standard states:

Effects: Equivalent to: return {data(), count};
or some variant of return {pointer, size}. As reported in GCC bug 120997 this results in a span that points to invalid stack memory. This can be reproduced on GCC 15.1 for subspan, first, and last: https://godbolt.org/z/r9nrdWscq.

A proposed fix (thanks to Jonathan Wakely) could look like this following:

return span<element_type>(data(), count);
for the affected paragraphs, which would explicitly specify the constructor used.

[2025-07-11; Jonathan adds proposed resolution]

The meaning of those Effects: paragraphs was changed for C++26 by P2447R6 which added the span(initializer_list) constructor. A simpler demo is:

bool a[5]{};
std::span<const bool> s(a);
std::span<const bool> s2 = s.first(5);
assert(s2.size() == 5); // OK in C++23, fails in C++26
assert(s2.data() == a); // OK in C++23, fails in C++26
The proposed resolution is to use R(data(), count) instead of {data(), count}. The former always (uniformly) means the same thing, but for the latter the meaning of list-initialization depends on the types. The list-initialization form will choose the initializer-list constructor when data() and count are both convertible to the element type.

[2025-08-21; Reflector poll]

Set status to Tentatively Ready after nine votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 23.7.2.2.4 [span.sub] as indicated:

    
    template<size_t Count> constexpr span<element_type, Count> first() const;
    

    -1- Mandates: Count <= Extent is true.

    -2- Hardened preconditions: Count <= size() is true.

    -3- Effects: Equivalent to: return R({data(), Count}); where R is the return type.

    
    template<size_t Count> constexpr span<element_type, Count> last() const;
    

    -4- Mandates: Count <= Extent is true.

    -5- Hardened preconditions: Count <= size() is true.

    -6- Effects: Equivalent to: return R({data() + (size() - Count), Count}); where R is the return type.

    
    template<size_t Offset, size_t Count = dynamic_extent>
      constexpr span<element_type, see below> subspan() const;
    

    -7- Mandates:

    Offset <= Extent && (Count == dynamic_extent || Count <= Extent - Offset)
    
    is true.

    -8- Hardened preconditions:

    Offset <= size() && (Count == dynamic_extent || Count <= size() - Offset)
    
    is true.

    -9- Effects: Equivalent to:

    return span<ElementType, see below>(
        data() + Offset, Count != dynamic_extent ? Count : size() - Offset);
    

    -10- Remarks: The second template argument of the returned span type is:

    Count != dynamic_extent ? Count
                            : (Extent != dynamic_extent ? Extent - Offset
                                                        : dynamic_extent)
    

    
    constexpr span<element_type, dynamic_extent> first(size_type count) const;
    

    -11- Hardened preconditions: count <= size() is true.

    -12- Effects: Equivalent to: return R({data(), count}); where R is the return type.

    
    constexpr span<element_type, dynamic_extent> last(size_type count) const;
    

    -13- Hardened preconditions: count <= size() is true.

    -14- Effects: Equivalent to: return R({data() + (size() - count), count}); where R is the return type.

    
    constexpr span<element_type, dynamic_extent> subspan(
      size_type offset, size_type count = dynamic_extent) const;
    

    -15- Hardened preconditions:

    offset <= size() && (count == dynamic_extent || count <= size() - offset
    
    is true.

    -16- Effects: Equivalent to:

    return R({data() + offset, count == dynamic_extent ? size() - offset :  count});
    
    where R is the return type.


4294(i). bitset(const CharT*) constructor needs to be constrained

Section: 22.9.2.2 [bitset.cons] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-07-12 Last modified: 2025-08-26

Priority: Not Prioritized

View all other issues in [bitset.cons].

View all issues with Tentatively Ready status.

Discussion:

This code might be ill-formed, with an error outside the immediate context that users cannot prevent:


#include <bitset>
struct NonTrivial { ~NonTrivial() { } };
static_assert( ! std::is_constructible_v<std::bitset<1>, NonTrivial*> );

The problem is that the bitset(const CharT*) constructor tries to instantiate basic_string_view<NonTrivial> to find its size_type, and that instantiation might ill-formed, e.g. if std::basic_string_view or std::char_traits has a static assert enforcing the requirement for their character type to be sufficiently char-like. 27.1 [strings.general] defines a char-like type as "any non-array trivially copyable standard-layout (6.9.1 [basic.types.general]) type T where is_trivially_default_constructible_v<T> is true."

[2025-08-21; Reflector poll]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 22.9.2.2 [bitset.cons] as indicated:

    
    template<class charT>
      constexpr explicit bitset(
        const charT* str,
        typename basic_string_view<charT>::size_type n = basic_string_view<charT>::npos,
        charT zero = charT(’0’),
        charT one = charT(’1’));
    

    -?- Constraints:

    • is_array_v<charT> is false,
    • is_trivially_copyable_v<charT> is true,
    • is_standard_layout_v<charT> is true, and
    • is_trivially_default_constructible_v<charT> is true.

    -8- Effects: As if by:

    bitset(n == basic_string_view<charT>::npos
              ? basic_string_view<charT>(str)
              : basic_string_view<charT>(str, n),
           0, n, zero, one)
    


4297(i). Missing permutable constraint for iterator overloads in Parallel Range Algorithms

Section: 26.4 [algorithm.syn], 26.7.8 [alg.remove], 26.8.5 [alg.partitions] Status: Tentatively Ready Submitter: Ruslan Arutyunyan Opened: 2025-06-27 Last modified: 2025-10-21

Priority: Not Prioritized

View other active issues in [algorithm.syn].

View all other issues in [algorithm.syn].

View all issues with Tentatively Ready status.

Discussion:

The P3179R9: Parallel Range Algorithms paper was accepted to C++ working draft for C++ 26. Unfortunately, there is an oversight for three algorithms — remove, remove_if and partition — where the permutable constraint is missing. This applies to "Iterator and Sentinel" overloads only. The issue exists in 26.4 [algorithm.syn] as well as in per-algorithm sections: 26.8.5 [alg.partitions] and 26.7.8 [alg.remove].

[2025-10-21; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 26.4 [algorithm.syn], header <algorithm>, as indicated:

    […]
    template<execution-policy Ep, random_access_iterator I, sized_sentinel_for<I> S,
             class Proj = identity, class T = projected_value_t<I, Proj>>
      requires permutable<I> &&
               indirect_binary_predicate<ranges::equal_to, projected<I, Proj>, const T*>
      subrange<I> remove(Ep& exec, I first, S last, const T& value,
                         Proj proj = {}); // freestanding-deleted
    template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
             class T = projected_value_t<iterator_t<R>, Proj>>
      requires permutable<iterator_t<R>> &&
               indirect_binary_predicate<ranges::equal_to,
                                         projected<iterator_t<R>, Proj>, const T*>
      borrowed_subrange_t<R>
        remove(Ep&& exec, R&& r, const T& value, Proj proj = {}); // freestanding-deleted                                     
    […]
    template<execution-policy Ep, random_access_iterator I, sized_sentinel_for<I> S,
             class Proj = identity, indirect_unary_predicate<projected<I, Proj>> Pred>
      requires permutable<I>
      subrange<I>
        remove_if(Ep& exec, I first, S last, Pred pred, Proj proj = {}); // freestanding-deleted
    template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
             indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
      requires permutable<iterator_t<R>>
      borrowed_subrange_t<R>
        remove_if(Ep& exec, R& r, Pred pred, Proj proj = {}); // freestanding-deleted
    […]
    template<execution-policy Ep, random_access_iterator I, sized_sentinel_for<I> S,
             class Proj = identity, indirect_unary_predicate<projected<I, Proj>> Pred>
      requires permutable<I>
      subrange<I>
        partition(Ep&& exec, I first, S last, Pred pred, Proj proj = {}); // freestanding-deleted
    template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
             indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
      requires permutable<iterator_t<R>>
      borrowed_subrange_t<R>
        partition(Ep&& exec, R&& r, Pred pred, Proj proj = {}); // freestanding-deleted
    […]
    
  2. Modify 26.7.8 [alg.remove] as indicated:

    […]
    template<execution-policy Ep, random_access_iterator I, sized_sentinel_for<I> S,
             class Proj = identity, class T = projected_value_t<I, Proj>>
      requires permutable<I> &&
               indirect_binary_predicate<ranges::equal_to, projected<I, Proj>, const T*>
      subrange<I> 
        ranges::remove(Ep& exec, I first, S last, const T& value, Proj proj = {});
    template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
             class T = projected_value_t<iterator_t<R>, Proj>>
      requires permutable<iterator_t<R>> &&
               indirect_binary_predicate<ranges::equal_to,
                                         projected<iterator_t<R>, Proj>, const T*>
      borrowed_subrange_t<R>
        ranges::remove(Ep&& exec, R&& r, const T& value, Proj proj = {});                                  
    […]
    template<execution-policy Ep, random_access_iterator I, sized_sentinel_for<I> S,
             class Proj = identity, indirect_unary_predicate<projected<I, Proj>> Pred>
      requires permutable<I>
      subrange<I>
        ranges::remove_if(Ep& exec, I first, S last, Pred pred, Proj proj = {});
    template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
             indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
      requires permutable<iterator_t<R>>
      borrowed_subrange_t<R>
        ranges::remove_if(Ep& exec, R& r, Pred pred, Proj proj = {});
    

    -1- Let E be […]

  3. Modify 26.8.5 [alg.partitions] as indicated:

    […]
    template<execution-policy Ep, random_access_iterator I, sized_sentinel_for<I> S,
             class Proj = identity, indirect_unary_predicate<projected<I, Proj>> Pred>
      requires permutable<I>
      subrange<I>
        ranges::partition(Ep&& exec, I first, S last, Pred pred, Proj proj = {});
    template<execution-policy Ep, sized-random-access-range R, class Proj = identity,
             indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
      requires permutable<iterator_t<R>>
      borrowed_subrange_t<R>
        ranges::partition(Ep&& exec, R&& r, Pred pred, Proj proj = {});
    

    -1- Let proj be identity{} for the overloads with no parameter named proj.


4299(i). Missing Mandates: part in optional<T&>::transform

Section: 22.5.4.7 [optional.ref.monadic] Status: Tentatively Ready Submitter: Giuseppe D'Angelo Opened: 2025-07-15 Last modified: 2025-10-16

Priority: Not Prioritized

View other active issues in [optional.ref.monadic].

View all other issues in [optional.ref.monadic].

View all issues with Tentatively Ready status.

Discussion:

In 22.5.4.7 [optional.ref.monadic] the specification of optional<T&>::transform is missing an additional part of the Mandates: element compared to the primary template's transform (in 22.5.3.8 [optional.monadic] p8); that is, is missing to enforce that the U type is a valid contained type for optional.

The definition of "valid contained type" comes from P2988R12. The paper amended the Mandates: element of the primary template's transform to use this definition. The fact that the same wording has not been applied to optional<T&>::transform as well looks like an oversight. I would suggest to apply it.

[2025-10-16; Reflector poll]

Set status to Tentatively Ready after six votes in favour during reflector poll.

As optional<remove_cv_t<invoke_result_t<F, T&>>> is part of the signature (return type), we never enter the body to trigger the Mandates, so it's already implicitly ill-formed if the result of f is not a valid contained type. It's worth clarifying that though."

Proposed resolution:

This wording is relative to N5014.

  1. Modify 22.5.4.7 [optional.ref.monadic] as indicated:

    template<class F>
      constexpr optional<remove_cv_t<invoke_result_t<F, T&>>> transform(F&& f) const;
    

    -4- Let U be remove_cv_t<invoke_result_t<F, T&>>.

    -5- Mandates: U is a valid contained type for optional. The declaration

    U u(invoke(std::forward<F>(f), *val ));
    

    is well-formed for some invented variable u.


4300(i). Missing Returns: element in optional<T&>::emplace

Section: 22.5.4.3 [optional.ref.assign] Status: Tentatively Ready Submitter: Giuseppe D'Angelo Opened: 2025-07-15 Last modified: 2025-08-28

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

The specification for optional<T&>::emplace in 22.5.4.3 [optional.ref.assign] is not specifying the returned value via a Returns: element; however the function does indeed return something (a T&). Such a Returns: element is there for the primary template's emplace (cf. 22.5.3.4 [optional.assign]).

[2025-08-27; Reflector poll]

Set status to Tentatively Ready after nine votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 22.5.4.3 [optional.ref.assign] as indicated:

    template<class U>
      constexpr T& emplace(U&& u) noexcept(is_nothrow_constructible_v<T&, U>);
    

    -4- Constraints: […]

    -5- Effects: Equivalent to: convert-ref-init-val(std::forward<U>(u)).

    -?- Returns: *val.


4301(i). condition_variable{_any}::wait_{for, until} should take timeout by value

Section: 32.7.4 [thread.condition.condvar], 32.7.5 [thread.condition.condvarany] Status: Tentatively Ready Submitter: Hui Xie Opened: 2025-07-19 Last modified: 2025-08-29

Priority: Not Prioritized

View other active issues in [thread.condition.condvar].

View all other issues in [thread.condition.condvar].

View all issues with Tentatively Ready status.

Discussion:

At the moment, both condition_variable and condition_variable_any's wait_for and wait_until take the timeout time_point/duration by const reference. This can cause surprising behaviour. Given the following example (thanks to Tim Song):

struct Task {
  system_clock::time_point deadline;
  // stuff
};

std::mutex mtx;
std::condition_variable cv;
std::priority_queue<Task, vector<Task>, CompareDeadlines> queue;

// thread 1
std::unique_lock lck(mtx);
if (queue.empty()) { cv.wait(lck); }
else { cv.wait_until(lck, queue.top().deadline); }

// thread 2
std::lock_guard lck(mtx);
queue.push(/* some task */);
cv.notify_one();

From the user's point of view, it is sufficiently locked on both threads. However, due to the fact that the time_point is taken by reference, and that both libc++ and libstdc++'s implementation will read the value again after waking up, this will read a dangling reference of the time_point.

Another example related to this issue:

We (libc++) recently received a bug report on condition_variable{_any}::wait_{for, until}.

Basically the user claims that these functions take time_point/duration by const reference, if the user modifies the time_point/duration on another thread with the same mutex, they can get unexpected return value for condition_variable, and data race for conditional_variable_any.

Bug report here.

Reproducer (libstdc++ has the same behaviour as ours) on godbolt.

std::mutex mutex;
std::condition_variable cv;
auto timeout = std::chrono::steady_clock::time_point::max();

// Thread 1:
std::unique_lock lock(mutex);
const auto status = cv.wait_until(lock, timeout);

// Thread 2:
std::unique_lock lock(mutex);
cv.notify_one();
timeout = std::chrono::steady_clock::time_point::min();

So basically the problem was that when we return whether there is no_timeout or timeout at the end of the function, we read the const reference again, which can be changed since the beginning of the function. For condition_variable, it is "unexpected results" according to the user. And in conditional_variable_any, we actually unlock the user lock and acquire our internal lock, then read the input again, so this is actually a data race.

For wait_for, the spec has

Effects: Equivalent to: return wait_until(lock, chrono::steady_clock::now() + rel_time);

So the user can claim our implementation is not conforming because the spec says there needs to be a temporary time_point (now + duration) created and since it should operate on this temporary time_point. There shouldn't be any unexpected behaviour or data race .

For wait_until it is unclear whether the spec has implications that implementations are allowed to read abs_time while the user's lock is unlocked.

it is also unclear if an implementation is allowed to return timeout if cv indeed does not wait longer than the original value of timeout. If it is not allowed, implementations will have to make a local copy of the input rel_time or abs_time, which defeats the purpose of taking arguments by const reference.

For both of the examples, Ville has a great comment in the reflector:

It seems like a whole bag of problems goes away if these functions just take the timeout by value?

libc++ implementers have strong preference just changing the API to take these arguments by value, and it is not an ABI break for us as the function signature has changed.

[2025-08-29; Reflector poll]

Set status to Tentatively Ready after nine votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 32.7.4 [thread.condition.condvar] as indicated:

    namespace std {
      class condition_variable {
      public:
        […]
        template<class Predicate>
          void wait(unique_lock<mutex>& lock, Predicate pred);
        template<class Clock, class Duration>
          cv_status wait_until(unique_lock<mutex>& lock,
                               const chrono::time_point<Clock, Duration>& abs_time);
        template<class Clock, class Duration, class Predicate>
          bool wait_until(unique_lock<mutex>& lock,
                          const chrono::time_point<Clock, Duration>& abs_time,
                          Predicate pred);
        template<class Rep, class Period>
          cv_status wait_for(unique_lock<mutex>& lock,
                             const chrono::duration<Rep, Period>& rel_time);
        template<class Rep, class Period, class Predicate>
          bool wait_for(unique_lock<mutex>& lock,
                        const chrono::duration<Rep, Period>& rel_time,
                        Predicate pred);    
        […]
      };
    }
    
    […]
    template<class Clock, class Duration>
      cv_status wait_until(unique_lock<mutex>& lock,
                           const chrono::time_point<Clock, Duration>& abs_time);
    

    -17- Preconditions: […]

    […]

    template<class Rep, class Period>
      cv_status wait_for(unique_lock<mutex>& lock,
                         const chrono::duration<Rep, Period>& rel_time);
    

    -23- Preconditions: […]

    […]

    template<class Clock, class Duration, class Predicate>
      bool wait_until(unique_lock<mutex>& lock,
                      const chrono::time_point<Clock, Duration>& abs_time,
                      Predicate pred);
    

    -29- Preconditions: […]

    […]

    template<class Rep, class Period, class Predicate>
      bool wait_for(unique_lock<mutex>& lock,
                    const chrono::duration<Rep, Period>& rel_time,
                    Predicate pred);    
    

    -35- Preconditions: […]

    […]

  2. Modify 32.7.5.1 [thread.condition.condvarany.general] as indicated:

    namespace std {
      class condition_variable_any {
      public:
        […]
        // 32.7.5.2 [thread.condvarany.wait], noninterruptible waits
        template<class Lock>
          void wait(Lock& lock);
        template<class Lock, class Predicate>
          void wait(Lock& lock, Predicate pred);
        
        template<class Lock, class Clock, class Duration>
          cv_status wait_until(Lock& lock, const chrono::time_point<Clock, Duration>& abs_time);
        template<class Lock, class Clock, class Duration, class Predicate>
          bool wait_until(Lock& lock, const chrono::time_point<Clock, Duration>& abs_time,
                          Predicate pred);
        template<class Lock, class Rep, class Period>
          cv_status wait_for(Lock& lock, const chrono::duration<Rep, Period>& rel_time);
        template<class Lock, class Rep, class Period, class Predicate>
          bool wait_for(Lock& lock, const chrono::duration<Rep, Period>& rel_time, Predicate pred);
       
        // 32.7.5.3 [thread.condvarany.intwait], interruptible waits
        template<class Lock, class Predicate>
          bool wait(Lock& lock, stop_token stoken, Predicate pred);
        template<class Lock, class Clock, class Duration, class Predicate>
          bool wait_until(Lock& lock, stop_token stoken,
                          const chrono::time_point<Clock, Duration>& abs_time, Predicate pred);
        template<class Lock, class Rep, class Period, class Predicate>
          bool wait_for(Lock& lock, stop_token stoken,
                        const chrono::duration<Rep, Period>& rel_time, Predicate pred);
      };
    }
    
  3. Modify 32.7.5.2 [thread.condvarany.wait] as indicated:

    […]
    template<class Lock, class Clock, class Duration>
      cv_status wait_until(Lock& lock, const chrono::time_point<Clock, Duration>& abs_time);
    

    -6- Effects: […]

    […]

    template<class Lock, class Rep, class Period>
      cv_status wait_for(Lock& lock, const chrono::duration<Rep, Period>& rel_time);
    

    -11- Effects: […]

    […]

    template<class Lock, class Clock, class Duration, class Predicate>
      bool wait_until(Lock& lock, const chrono::time_point<Clock, Duration>& abs_time,
                      Predicate pred);
    

    -16- Effects: […]

    […]

    template<class Lock, class Rep, class Period, class Predicate>
      bool wait_for(Lock& lock, const chrono::duration<Rep, Period>& rel_time, Predicate pred);
    

    -19- Effects: […]

  4. Modify 32.7.5.3 [thread.condvarany.intwait] as indicated:

    […]
    template<class Lock, class Clock, class Duration, class Predicate>
      bool wait_until(Lock& lock, stop_token stoken,
                      const chrono::time_point<Clock, Duration>& abs_time, Predicate pred);
    

    -7- Effects: […]

    […]

    template<class Lock, class Rep, class Period, class Predicate>
      bool wait_for(Lock& lock, stop_token stoken,
                    const chrono::duration<Rep, Period>& rel_time, Predicate pred);
    

    -13- Effects: […]


4305(i). Missing user requirements on type_order template

Section: 17.12.7 [compare.type] Status: Tentatively Ready Submitter: Daniel Krügler Opened: 2025-07-27 Last modified: 2025-10-14

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

The recently approved paper P2830R10 proposes to add the new type_order type traits to 17.12 [cmp] (and thus outside of 21.3 [type.traits]), which has the subtle and most likely unintended effect, that it doesn't fall under the general requirement expressed in 21.3.2 [meta.rqmts] p4,

Unless otherwise specified, the behavior of a program that adds specializations for any of the templates specified in 21.3.2 [meta.rqmts] is undefined.

and so in principle the explicit allowance specified in 16.4.5.2.1 [namespace.std] p2,

Unless explicitly prohibited, a program may add a template specialization for any standard library class template to namespace std […]

holds. So we need to add extra wording to the type_order specification in 17.12.7 [compare.type] to prohibit such program specializations.

This was reported shortly before the Sofia meeting during reflector discussion but seems to be forgotten before the final paper appeared on plenary.

During the reflector discussion two possible ways to solve this issue were pointed out:

  1. The most simple one would mimic the wording in 21.3.2 [meta.rqmts] p4 quoted above.

  2. Instead of introducing just another undefined opportunity to run into undefined behaviour it has been pointed out that we could follow the approach taken by std::initializer_list and make the program ill-formed in this case, as specified in 17.11.2 [initializer.list.syn] p2:

    If an explicit specialization or partial specialization of initializer_list is declared, the program is ill-formed.

Jonathan Wakely responded to the reflector discussion:

I think ill-formed would be better. It shouldn't be difficult for implementations to have special cases that are disallowed.

Given the already existing experience with std::initializer_list the proposed wording below therefore follows the ill-formed program approach.

[2025-10-14; Reflector poll]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 17.12.7 [compare.type] as indicated:

    template<class T, class U>
      struct type_order;
    

    -2- The name type_order denotes a Cpp17BinaryTypeTrait (21.3.2 [meta.rqmts]) with a base characteristic of integral_constant<strong_ordering, TYPE-ORDER(T, U)>.

    -?- If an explicit specialization or partial specialization of type_order is declared, the program is ill-formed.

    -3- Recommended practice: The order should be lexicographical on parameter-type-lists and template argument lists.


4309(i). How are two seq_cst operations ordered in the single total order if these two operations are unsequenced?

Section: 32.5.4 [atomics.order] Status: Tentatively NAD Submitter: jim x Opened: 2025-08-06 Last modified: 2025-10-23

Priority: Not Prioritized

View other active issues in [atomics.order].

View all other issues in [atomics.order].

View all issues with Tentatively NAD status.

Discussion:

Consider this snippet code:

std::atomic<int> flag = {0};
std::atomic<int> turn = {0};

if(flag + turn){}

The loads of flag and turn as the operands of + are unsequenced according to [intro.execution] p10.

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.

However, 32.5.4 [atomics.order] p4 says:

There is a single total order S on all memory_order::seq_cst operations, including fences, that satisfies the following constraints.

Then, it says that:

First, if A and B are memory_order::seq_cst operations and A strongly happens before B, then A precedes B in S.

According to the first sentence, the load of flag and the load of turn do have an order in the single total order S since they are both memory_order::seq_cst operations. However, since they are unsequenced, the second sentence does not apply to them. So, what's the order of them in S? Is the order of them in S unspecified?

Suggested Resolution:

We may want to say the order of such operations is indeterminate in the single total order. That is, either A precedes B or B precedes A, but it is unspecified which.

[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]

The loads are both done within functions calls, so are at worst indeterminately sequenced. So we already say what the suggested resolution wants.

Proposed resolution:


4312(i). Const and value category mismatch for allocator_arg_t/allocator_arg in the description of uses-allocator construction

Section: 20.2.8.2 [allocator.uses.construction] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-08-06 Last modified: 2025-10-14

Priority: Not Prioritized

View other active issues in [allocator.uses.construction].

View all other issues in [allocator.uses.construction].

View all issues with Tentatively Ready status.

Discussion:

Currently, 20.2.8.2 [allocator.uses.construction] bullet 2.2 states:

Otherwise, if T has a constructor invocable as T(allocator_arg, alloc, args...) (leading-allocator convention), […]

However, when forming construction arguments in the utility functions, we're actually using cv-unqualified rvalue of allocator_arg_t, which can be inferred from using plain allocator_arg_t but not const allocator_arg_t& in 20.2.8.2 [allocator.uses.construction] bullet 5.2.

It seems that such mismatch was present even since C++11 (per N3337 [allocator.uses.construction]/1.2). If the use of plain allocator_arg_t is considered correct, I think we should fix the description.

[2025-10-14; Reflector poll]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Unless the std::allocator_arg tag object is not supposed to be used, wouldn't it make more sense to preserve the "if T has a constructor invocable as T(allocator_arg, alloc, args...)" wording and change every allocator_arg_t into const allocator_arg_t&, so that we check for construction from the const tag object, and then actually use a const value in the constructor arguments. Strongly don't care though.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 20.2.8.2 [allocator.uses.construction] as indicated:

    -2- The following utility functions support three conventions for passing alloc to a constructor:

    1. (2.1) — […]

    2. (2.2) — Otherwise, if T has a constructor invocable as T(allocator_argallocator_arg_t{}, alloc, args...) (leading-allocator convention), then uses-allocator construction chooses this constructor form.

    3. (2.3) — […]


4317(i). The meaning of "resource" in the Cpp17Destructible requirements is undefined

Section: 16.4.4.2 [utility.arg.requirements] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-08-15 Last modified: 2025-10-14

Priority: Not Prioritized

View other active issues in [utility.arg.requirements].

View all other issues in [utility.arg.requirements].

View all issues with Tentatively Ready status.

Discussion:

The meaning of "resource" in the Cpp17Destructible requirements cannot be inferred from the standard wording and it seems unlikely that the standard will determine its meaning in the future. What are considered as resources generally depends on users' intent, so the standard shouldn't determine the well-definedness of a program execution due to it. Moreover, the wording doesn't seem to consider shared ownership, which can be represented by shared_ptr.

[2025-10-14; Reflector poll]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 16.4.4.2 [utility.arg.requirements], Table 35 [tab:cpp17.destructible] as indicated:

    Table 35 — Cpp17Destructible requirements [tab:cpp17.destructible]
    Expression Post-condition
    u.~T() All resources owned by u are reclaimed, nNo exception is propagated.
    [Note 3: Array types and non-object types are not Cpp17Destructible. — end note]

4318(i). Have hive::erase_if reevaluate end() to avoid UB

Section: 23.3.9.6 [hive.erasure] Status: Tentatively Ready Submitter: Frank Birbacher Opened: 2025-08-16 Last modified: 2025-08-29

Priority: Not Prioritized

View all other issues in [hive.erasure].

View all issues with Tentatively Ready status.

Discussion:

Background: https://github.com/cplusplus/draft/pull/8162

For 23.3.9.6 [hive.erasure] p2, the defining code must not cache the end-iterator. In case the last element of the sequence is removed, the past-the-end iterator will be invalidated. This will trigger UB in the loop condition. Instead, re-evaluate end() each time.

[2025-08-29; Reflector poll]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

[Drafting note: There are other ways to fix this code while keeping the caching behaviour, but I don't see any particular reason to do so for the definition of the effects.]

  1. Modify 23.3.9.6 [hive.erasure] as indicated:

    template<class T, class Allocator, class Predicate>
      typename hive<T, Allocator>::size_type
        erase_if(hive<T, Allocator>& c, Predicate pred);
    

    -2- Effects: Equivalent to:

    auto original_size = c.size();
    for (auto i = c.begin(), last = c.end(); i != lastc.end(); ) {
      if (pred(*i)) {
        i = c.erase(i);
      } else {
        ++i;
      }
    }
    return original_size - c.size();
    

4321(i). How are evaluations occurring within a store and a load operation ordered where the store synchronized with the load

Section: 32.5.8.2 [atomics.types.operations] Status: Tentatively NAD Submitter: jim x Opened: 2025-08-20 Last modified: 2025-10-23

Priority: Not Prioritized

View other active issues in [atomics.types.operations].

View all other issues in [atomics.types.operations].

View all issues with Tentatively NAD status.

Discussion:

Consider this example:

std::atomic<int> v = 0;
// thread 1:
v.store(1, memory_order::release); // #1
// thread 2:
v.load(memory_order::acquire); // #2

Say, #2 reads the value written by #1, #1 synchronizes with #2. According to 6.10.2.2 [intro.races] p7:

An evaluation A happens before an evaluation B (or, equivalently, B happens after A) if either

  1. (7.1) — […]

  2. (7.2) — A synchronizes with B, or

  3. (7.3) — […]

So, #1 happens before B. However, 6.10.1 [intro.execution] p12 says:

For each

  1. (12.1) — function invocation,

  2. (12.2) — […]

  3. (12.3) — […]

F, each evaluation that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any) is either sequenced before all evaluations that occur within F or sequenced after all evaluations that occur within F;

Because both v.store(...) and v.load(...) are function invocations, and we can think that the member functions comprise some evaluations to form the operation, therefore, how are these evaluations that occur within the store ordered with those within the load?

The rule only says the store synchronizes with the load, hence, the evaluation of the function call expression v.store(...) happens before the evaluation of the function call expression v.load(...), but how about these evaluations occurring within these functions?

A possible resolution might be: The order between all evaluations occurring within a function invocation and another evaluation B is determined by how the evaluation of the function call expression is ordered in relation to the expression B.

For example, if v.store() happens-before E, then all evaluations occurring within the store happen-before E. As well, v.store(...) synchronizes with v.load(...), then all evaluations occurring within v.store(...) synchronize with all evaluations occurring within v.load(...).

[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]

The load/store functions invocations participate in happens-before as atomic (indivisible) units.

Proposed resolution:


4328(i). Remove note in §[exec.sched] regarding waiting for completion of scheduled operations

Section: 33.6 [exec.sched] Status: Tentatively Ready Submitter: Lewis Baker Opened: 2025-08-25 Last modified: 2025-10-23

Priority: Not Prioritized

View other active issues in [exec.sched].

View all other issues in [exec.sched].

View all issues with Tentatively Ready status.

Discussion:

The note at the end of 33.6 [exec.sched] says:

[Note: The ability to wait for completion of submitted function objects can be provided by the associated execution resource of the scheduler — end note]

The suggestion that the execution resource should be used to join/wait on scheduled work is problematic in situations that may involve more than one execution context, as an execution resource having an empty queue of scheduled work does not necessarily imply that tasks currently running on another execution context will not later schedule additional work on this execution resource.

With the introduction of counting_scope with P3149 we now have a better recommended way of waiting for tasks that use a resource (including execution resources) to complete.

The note as it stands represents bad guidance and should either be removed or updated to refer to counting_scope and simple_counting_scope (33.14.2 [exec.counting.scopes]).

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

"What is this telling me as a user? That a custom execution resource can add a non-standard 'wait for completion' facility?"

Proposed resolution:

This wording is relative to N5014.

  1. Modify 33.6 [exec.sched] as indicated:

    -7- A scheduler type's destructor shall not block pending completion of any receivers connected to the sender objects returned from schedule.

    [Note 1: The ability to wait for completion of submitted function objects can be provided by the associated execution resource of the scheduler. — end note]


4337(i). co_await change_coroutine_scheduler(s) requires assignable

Section: 33.13.6.5 [task.promise] Status: Tentatively NAD Submitter: Dietmar Kühl Opened: 2025-08-31 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [task.promise].

View all other issues in [task.promise].

View all issues with Tentatively NAD status.

Discussion:

The specification of change_coroutine_scheduler(sched) uses std::exchange to put the scheduler into place (in 33.13.6.5 [task.promise] paragraph 11). The problem is that std::exchange(x, v) expects x to be assignable from v but there is no requirement for scheduler to be assignable.

[2025-10-23; Reflector poll. Status → Tentatively NAD .]

"scheduler requires copyable which requires assignment."

[2025-10-24; Resolves US 256-383]

Proposed resolution:

Change the wording in 33.13.6.5 [task.promise] paragraph 11 to avoid the use of std::exchange and transfer the using construction:

template<class Sch>
  auto await_transform(change_coroutine_scheduler<Sch> sch) noexcept;

-11- Effects: Equivalent to:

return await_transform(just(exchange(SCHED(*this), scheduler_type(sch.scheduler))), *this);
auto* s{address_of(SCHED(*this))};
auto rc{std::move(*s)};
s->~scheduler_type();
new(s) scheduler_type(std::move(sch));
return std::move(rc);


4340(i). task::promise_type::unhandled_stopped() should be noexcept

Section: 33.13.6.5 [task.promise] Status: Tentatively Ready Submitter: Dietmar Kühl Opened: 2025-08-31 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [task.promise].

View all other issues in [task.promise].

View all issues with Tentatively Ready status.

Discussion:

The function task::promise_type::unhandled_stopped() is called from set_stopped() of a receiver and calls set_stopped itself. These functions are required to be noexcept. Thus, unhandled_stopped() can't throw an exception and should be marked noexcept. All other declarations of unhandled_stopped() are already marked noexcept but task::promise_type::unhandled_stopped() isn't.

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

[2025-10-24; Resolves US 252-387]

Proposed resolution:

In the synopsis in 33.13.6.5 [task.promise] add noexcept to the declaration of task::promise_type::unhandled_stopped():

namespace std::execution {
  template<class T, class Environment>
  class task<T, Environment>::promise_type {
     ...
    coroutine_handle<> unhandled_stopped() noexcept;
    ...
  };
}

In the specification in 33.13.6.5 [task.promise] paragraph 13 add noexcept:

coroutine_handle<> unhandled_stopped() noexcept;

-13- Effects: Completes the asynchronous operation associated with STATE(*this) by invoking set_stopped(std::move(RCVR(*this))).


4341(i). Missing rvalue reference qualification for task::connect()

Section: 33.13.6.2 [task.class] Status: Tentatively Ready Submitter: Dietmar Kühl Opened: 2025-08-31 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [task.class].

View all other issues in [task.class].

View all issues with Tentatively Ready status.

Discussion:

Coroutines can't be copied. Thus, a task can be connect() just once. To represent that task::connect() should be rvalue reference qualified but currently it isn't.

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

"It's nice to rvalue qualify such a function, but it is not strictly necessary."

[2025-10-24; Resolves US 244-375]

Proposed resolution:

In the synopsis in 33.13.6.2 [task.class] add rvalue reference qualification to task::connect():

namespace std::execution {
  template<class T, class Environment>
  class task {
    ...
    template<receiver Rcvr>
        state<Rcvr> connect(Rcvr&& rcvr) &&;
    ...
  }
}

In the specification in 33.13.6.3 [task.members] paragraph 3 add rvalue reference qualification to task::connect():

template<receiver Rcvr>
    state<Rcvr> connect(Rcvr&& rcvr) &&;

-3- Precondition: bool(handle) is true.

-4- Effects: Equivalent to:

    return state<Rcvr>(exchange(handle, {}), std::forward<Rcvr>(recv));


4342(i). Missing rvalue reference qualification for task_scheduler::ts-sender::connect()

Section: 33.13.5 [exec.task.scheduler] Status: Tentatively Ready Submitter: Dietmar Kühl Opened: 2025-09-01 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [exec.task.scheduler].

View all other issues in [exec.task.scheduler].

View all issues with Tentatively Ready status.

Discussion:

The result of schedule(sched) for a scheduler sched is only required to be movable. An object of this type may need to be forwarded to an operation state constructor by task_scheduler::ts-sender::connect. Thus, this function should be qualified with an rvalue reference.

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

[2025-10-24; Resolves US 237-369]

Proposed resolution:

Add an rvalue qualifier to the declaration of connect in 33.13.5 [exec.task.scheduler] paragraph 8:

namespace std::execution {
  class task_scheduler::ts-sender {     // exposition only
  public:
    using sender_concept = sender_t;

    template<receiver Rcvr>
      state<Rcvr> connect(Rcvr&& rcvr) &&;
  };
}

In the specification in 33.13.5 [exec.task.scheduler] paragraph 10 add an rvalue qualifier to connect:

template<receiver Rcvr>
  state<Rcvr> connect(Rcvr&& rcvr) &&;

-10- Effects: Let r be an object of a type that models receiver and whose completion handlers result in invoking the corresponding completion handlers of rcvr or copy thereof. Returns an object of type state<Rcvr> containing an operation state object initialized with connect(SENDER(*this), std::move(r)).


4343(i). Missing default template arguments for task

Section: 33.13.6.2 [task.class] Status: Tentatively Ready Submitter: Dietmar Kühl Opened: 2025-09-01 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [task.class].

View all other issues in [task.class].

View all issues with Tentatively Ready status.

Discussion:

The design discussion of task describes defaults for the two template parameters T and Environment of task but these defaults are not reflected in the synopsis of 33.13.6.2 [task.class]. This is an oversight and should be fixed. The default for T should be void and the default for Environment should be env<> (the design paper used empty_env but this struct was replaced by the class template env by P3325R5).

There could be a counter argument to defining a default for the Environment template parameter: this type is used to determine various customizations of task, e.g., the allocator_type, the scheduler_type, and the stop_source_type. Leaving the type a required argument means that a future standard could choose a possibly better default than the types determined when the Environment doesn't define them. On the other hand, a future standard could provide a suitable alias with modified types under a different name and/or a different namespace. Based on the LEWG discussion on 2025-08-26 the direction is to add the default arguments.

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

[2025-10-24; Resolves US 243-376]

Proposed resolution:

Add default template arguments for task for T = void and Environment = env<> in the synopsis of 33.13.6.2 [task.class]:

namespace std::execution {
  template<class T = void, class Environment = env<>>
  class task {
     ...
  };
}


4345(i). task::promise_type::return_value default template parameter

Section: 33.13.6.5 [task.promise] Status: Tentatively Ready Submitter: Dietmar Kühl Opened: 2025-09-01 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [task.promise].

View all other issues in [task.promise].

View all issues with Tentatively Ready status.

Discussion:

The template parameter V of task::promise_type::return_value doesn't have a default template argument specified. Specifying a default template argument of T would enable use of co_return { ... } which would be consistent with normal return statements. This feature was not discussed in the design paper but based on the LEWG discussion on 2025-08-26 it is considered to be more a bug fix than a new feature.

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

[2025-10-24; Resolves US 251-388]

Proposed resolution:

Add a default template argument of T to the template parameter V of task::promise_type::return_value in the synopsis of 33.13.6.5 [task.promise]:

namespace std::execution {
  template<class T, class Environment>
  class task<T, Environment>::promise_type {
     ...
    template<typename V = T>
    void return_value(V&& value);
    ...
  };

}


4346(i). task::promise_type::return_void/value lack a specification

Section: 33.13.6.5 [task.promise] Status: Tentatively Ready Submitter: Dietmar Kühl Opened: 2025-09-01 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [task.promise].

View all other issues in [task.promise].

View all issues with Tentatively Ready status.

Discussion:

The synopsis for std::execution::task<T, E>::promise_type declares return_void() or return_value(V&&). However, there is no specification of what these functions actually do. return_void() doesn’t need to do anything at all. return_value(V&& v) needs to store v into the result.

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

[2025-10-24; Resolves US 250-389]

Proposed resolution:

Insert the following paragraphs in 33.13.6.5 [task.promise] after the specification of unhandled_stopped:

coroutine_handle<> unhandled_stopped();

-13- Effects: Completes the asynchronous operation associated with STATE(*this) by invoking set_stopped(std::move(RCVR(*this))).

-14- Returns: noop_coroutine().

void return_void();

-?- Effects: does nothing.


template<class V>
  void return_value(V&& v);

-?- Effects: Equivalent to result.emplace(std::forward<V>(v)).

unspecified get_env() const noexcept;

-15- Returns: An object env such that queries are forwarded as follows:


4349(i). task is not actually started lazily

Section: 33.13.6.5 [task.promise] Status: Tentatively Ready Submitter: Dietmar Kühl Opened: 2025-09-01 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [task.promise].

View all other issues in [task.promise].

View all issues with Tentatively Ready status.

Discussion:

The wording for task<...>::promise_type::initial_suspend in 33.13.6.5 [task.promise] paragraph 6 (second bullet) may imply that a task is eagerly started, i.e., that the awaiter return from initial_suspend() immediately starts the scheduling operation and cause the task to be resumed. At the very least the second bullet of the wording should be clarified such that the scheduling operation is only started when the coroutine gets resumed.

An alternative resolution it have initial_suspend() return std::suspend_always implicitly requiring that the task gets start()ed from the correct execution context. This approach has the advantage of avoiding unnecessary scheduling operations for the likely common case when tasks are started from the correct context.

[2025-10-18; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

[2025-10-24; Resolves US 258-381]

Proposed resolution:

Change the declaration of initial_suspend() in the synopsis of 33.13.6.5 [task.promise] to use suspend_always, directly provide a definition, and add various qualifiers:

namespace std::execution {
  template<class T, class Environment>
  class task<T, Environment>::promise_type {
    ...
    autostatic constexpr suspend_always initial_suspend() noexcept;{ return {}; }
    ...
  };

}

Remove 33.13.6.5 [task.promise] paragraph 6 entirely:

auto initial_suspend() noexcept;

-6- Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for

-6.1- - the calling coroutine to be suspended,

-6.2- - the coroutine to be resumed on an execution agent of the execution resource associated with SCHED(*this).


4351(i). integral-constant-like needs more remove_cvref_t

Section: 23.7.2.1 [span.syn] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-09-05 Last modified: 2025-10-17

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

P2781R9 tweaked the definition of integral-constant-like to work with constant_wrapper, like so:

template<class T>
    concept integral-constant-like =                    // exposition only
      is_integral_v<remove_cvref_t<decltype(T::value)>> &&
      !is_same_v<bool, remove_const_t<decltype(T::value)>> &&
      convertible_to<T, decltype(T::value)> &&
      equality_comparable_with<T, decltype(T::value)> &&
      bool_constant<T() == T::value>::value &&
      bool_constant<static_cast<decltype(T::value)>(T()) == T::value>::value;

This was done so that cw<5> models the concept, but it needs an additional tweak so that cw<true> does not model it.

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after eight votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 23.7.2.1 [span.syn] as indicated:

    template<class T>
        concept integral-constant-like =                    // exposition only
          is_integral_v<remove_cvref_t<decltype(T::value)>> &&
          !is_same_v<bool, remove_cvrefconst_t<decltype(T::value)>> &&
          convertible_to<T, decltype(T::value)> &&
          equality_comparable_with<T, decltype(T::value)> &&
          bool_constant<T() == T::value>::value &&
          bool_constant<static_cast<decltype(T::value)>(T()) == T::value>::value;
    

4362(i). Inconsistent usage of constexpr for inplace_stop_token and inplace_stop_source

Section: 32.3.8 [stoptoken.inplace] Status: Tentatively NAD Submitter: Lewis Baker Opened: 2025-08-28 Last modified: 2025-10-17

Priority: Not Prioritized

View all issues with Tentatively NAD status.

Discussion:

The inplace_stop_source::get_token() member function is declared constexpr, but there are no constexpr member-functions declared on inplace_stop_token, making the utility of being able to call this member function during constant evaluation limited.

Should the member functions of inplace_stop_token also be declared constexpr? i.e. operator==, swap(), stop_possible() and stop_requested().

The operator== and stop_possible() and swap() member functions should be able to be made constexpr trivially as they are just required to compare/modify pointers to the associated stop source.

The stop_requested() member function is specified to be equivalent to calling stop_requested() on the associated inplace_stop_source (if any), which is not currently declared constexpr primarily because its implementation requires synchronisation/atomic operations.

Now that std::atomic operations are now constexpr, it may be possible/appropriate for stop_requested() on both inplace_stop_source and inplace_stop_token to also be declared constexpr.

[2025-10-17; Reflector poll. Status changed: New → Tentatively NAD.]

This allows constant-initializing a token, it's basically a constructor. Other member functions don't need to be constexpr, similar to how std::mutex::lock() doesn't need to be constexpr for constant-init of std::mutex to be useful.

Proposed resolution:

This wording is relative to N5014.

[Drafting note:: This is the minimum proposed wording change. Additionally, consider adding constexpr to the declaration of inplace_stop_token::stop_requested() (in 32.3.8.1 [stoptoken.inplace.general] and 32.3.8.2 [stoptoken.inplace.mem]) and to inplace_stop_source::stop_requested() (in 32.3.9.1 [stopsource.inplace.general] and 32.3.9.3 [stopsource.inplace.mem])]

  1. Modify 32.3.8.1 [stoptoken.inplace.general], class inplace_stop_token synopsis, as indicated:

    namespace std {
      class inplace_stop_token {
      public:
        template<class CallbackFn>
          using callback_type = inplace_stop_callback<CallbackFn>;
        
        constexpr inplace_stop_token() = default;
        constexpr bool operator==(const inplace_stop_token&) const = default;
        
        // 32.3.8.2 [stoptoken.inplace.mem], member functions
        bool stop_requested() const noexcept;
        constexpr bool stop_possible() const noexcept;
        constexpr void swap(inplace_stop_token&) noexcept;
        
      private:
        const inplace_stop_source* stop-source = nullptr; // exposition only
      };
    }
    
  2. Modify 32.3.8.2 [stoptoken.inplace.mem] as indicated:

    [Drafting note:: As a drive-by fix this adds the missing return type bool to the stop_possible() prototype]

    constexpr void swap(inplace_stop_token& rhs) noexcept;
    

    -1- Effects: Exchanges the values of stop-source and rhs.stop-source.

    […]
    constexpr bool stop_possible() const noexcept;
    

    -4- Returns: stop-source != nullptr.


4363(i). transform_sender comparing types ignoring cv-qualifiers doesn't take into account differences in value category

Section: 33.9.6 [exec.snd.transform] Status: Tentatively NAD Submitter: Lewis Baker Opened: 2025-08-28 Last modified: 2025-10-23

Priority: Not Prioritized

View all issues with Tentatively NAD status.

Discussion:

In 33.9.6 [exec.snd.transform] p1, the specification for transform_sender() states:

Let transformed-sndr be the expression

dom.transform_sender(std::forward<Sndr>(sndr), env...)

if that expression is well-formed; otherwise,

default_domain().transform_sender(std::forward<Sndr>(sndr), env...)

Let final-sndr be the expression transformed-sndr if transformed-sndr and sndr have the same type ignoring cv-qualifiers; otherwise, it is the expression transform_sender(dom, transformed-sndr, env...).

However, the use of the phrase "have the same type ignoring cv-qualifiers" asks to compare the types without const or volatile qualifiers, but doesn't take into account differences in value category of the types of these expressions.

For example sndr might have type T&& and transformed-sndr might return a new prvalue of type T.

My interpretation of the current wording is that I should apply the test same_as<remove_cv_t<decltype(sndr)>, remove_cv_t<decltype(transformed-sndr)>>.

However, remove_cv_t does not remove reference-qualifiers from a type Sndr&& (which in the above example, is the type of sndr), and thus would compare as different to a transform-sndr type of Sndr.

I believe the intention is that this should instead use same_as<remove_cvref_t<decltype(sndr)>, remove_cvref_t<decltype(transformed-sndr)>>. For example, the implementation in NVIDIA's stdexec repository uses same_as<decay_t<T>, decay_t<U>> for this check.

The wording should be modified to use a phrase that removes both reference and cv-qualifiers when comparing types.

[2025-10-23; Reflector poll. Status → Tentatively NAD .]

"NAD, expressions never have reference type."

Proposed resolution:

This wording is relative to N5014.

  1. Modify 33.9.6 [exec.snd.transform] as indicated:

    namespace std::execution {
      template<class Domain, sender Sndr, queryable... Env>
        requires (sizeof...(Env) <= 1)
      constexpr sender decltype(auto) transform_sender(Domain dom, Sndr&& sndr, const Env&... env)
        noexcept(see below);
    }
    

    -1- Let transformed-sndr be the expression

    dom.transform_sender(std::forward<Sndr>(sndr), env...)
    

    if that expression is well-formed; otherwise,

    default_domain().transform_sender(std::forward<Sndr>(sndr), env...)
    

    Let final-sndr be the expression transformed-sndr if transformed-sndr and sndr have the same decayed type ignoring cv-qualifiers; otherwise, it is the expression transform_sender(dom, transformed-sndr, env...).


4366(i). Heterogeneous comparison of expected may be ill-formed

Section: 22.8.6.8 [expected.object.eq], 22.8.7.8 [expected.void.eq] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2025-09-06 Last modified: 2025-10-16

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

These comparison functions all explicitly static_cast the result of the underlying comparison to bool. However, the Constraints only require the implicit conversion, not the explicit one (i.e., "convertible to bool" rather than "models boolean-testable").

This means that in some pathological cases it will lead to hard errors (demo):

#include <expected>

struct E1 {};
struct E2 {};

struct Bool {
  operator bool() const;
  explicit operator bool() = delete;
};
Bool operator==(E1, E2);

int main() {
  std::unexpected e1{E1{}};
  std::unexpected e2{E2{}};
  return std::expected<int, E1>{e1} == e2; // fire
}

It is reasonable to specify return consistency with actual Constraints.

[2025-10-16; Reflector poll]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Related to LWG 4366(i), but the wording styles are inconsistent. optional uses "Effects: Equivalent to ..." and expected just uses Returns:.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 22.8.6.8 [expected.object.eq] as indicated:

    template<class T2> friend constexpr bool operator==(const expected& x, const T2& v);
    

    -3- Constraints: T2 is not a specialization of expected. The expression *x == v is well-formed and its result is convertible to bool.

    [Note 1: T need not be Cpp17EqualityComparable. — end note]

    -4- Returns: If x.has_value() is true, && static_cast<bool>(*x == v); otherwise false.

    template<class E2> friend constexpr bool operator==(const expected& x, const unexpected<E2>& e);
    

    -5- Constraints: The expression x.error() == e.error() is well-formed and its result is convertible to bool.

    -6- Returns: If !x.has_value() is true, && static_cast<bool>(x.error() == e.error()); otherwise false.

  2. Modify 22.8.7.8 [expected.void.eq] as indicated:

    template<class T2, class E2> requires is_void_v<T2>
      friend constexpr bool operator==(const expected& x, const expected<T2, E2>& y);
    

    -1- Constraints: The expression x.error() == y.error() is well-formed and its result is convertible to bool.

    -2- Returns: If x.has_value() does not equal y.has_value(), false; otherwise if x.has_value() is true, true; otherwise || static_cast<bool>(x.error() == y.error()).

    template<class E2>
      friend constexpr bool operator==(const expected& x, const unexpected<E2>& e);
    

    -3- Constraints: The expression x.error() == e.error() is well-formed and its result is convertible to bool.

    -4- Returns: If !x.has_value() is true, && static_cast<bool>(x.error() == e.error()) ; otherwise false.


4370(i). Comparison of optional<T> to T may be ill-formed

Section: 22.5.9 [optional.comp.with.t] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2025-09-06 Last modified: 2025-10-16

Priority: Not Prioritized

View all other issues in [optional.comp.with.t].

View all issues with Tentatively Ready status.

Discussion:

When comparing an optional with its value type, the current wording specifies that the result is the ternary expression of x.has_value() ? *x == v : false, where *x == v returns a result that can be implicitly converted to bool.

However, when the result can also be constructed using bool (which is common), the ternary operation will be ill-formed due to ambiguity (demo):

#include <optional>

struct Bool {
  Bool(bool);
  operator bool() const;
};

struct S {
  Bool operator==(S) const;
};

int main() {
  return std::optional<S>{} == S{}; // fire
}

[2025-10-16; Reflector poll]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

"Alternatively could keep the conditional operator but cast one side to bool, but that would do an explicit conversion, which might not be what we want."

"Should just require boolean-testable."

Related to LWG 4366(i).

Proposed resolution:

This wording is relative to N5014.

  1. Modify 22.5.9 [optional.comp.with.t] as indicated:

    template<class T, class U> constexpr bool operator==(const optional<T>& x, const U& v);
    

    -1- Constraints: U is not a specialization of optional. The expression *x == v is well-formed and its result is convertible to bool.

    [Note 1: T need not be Cpp17EqualityComparable. — end note]

    -2- Effects: Equivalent to: return x.has_value() ? *x == v : false;

    if (x.has_value())
      return *x == v;
    return false;
    
    template<class T, class U> constexpr bool operator==(const T& v, const optional<U>& x);
    

    -3- Constraints: T is not a specialization of optional. The expression v == *x is well-formed and its result is convertible to bool.

    -4- Effects: Equivalent to: return x.has_value() ? v == *x : false;

    if (x.has_value())
      return v == *x;
    return false;
    
    template<class T, class U> constexpr bool operator!=(const optional<T>& x, const U& v);
    

    -5- Constraints: U is not a specialization of optional. The expression *x != v is well-formed and its result is convertible to bool.

    -6- Effects: Equivalent to: return x.has_value() ? *x != v : true;

    if (x.has_value())
      return *x != v;
    return true;
    
    template<class T, class U> constexpr bool operator!=(const T& v, const optional<U>& x);
    

    -7- Constraints: T is not a specialization of optional. The expression v != *x is well-formed and its result is convertible to bool.

    -8- Effects: Equivalent to: return x.has_value() ? v != *x : true;

    if (x.has_value())
      return v != *x;
    return true;
    
    template<class T, class U> constexpr bool operator<(const optional<T>& x, const U& v);
    

    -9- Constraints: U is not a specialization of optional. The expression *x < v is well-formed and its result is convertible to bool.

    -10- Effects: Equivalent to: return x.has_value() ? *x < v : true;

    if (x.has_value())
      return *x < v;
    return true;
    
    template<class T, class U> constexpr bool operator<(const T& v, const optional<U>& x);
    

    -11- Constraints: T is not a specialization of optional. The expression v < *x is well-formed and its result is convertible to bool.

    -12- Effects: Equivalent to: return x.has_value() ? v < *x : false;

    if (x.has_value())
      return v < *x;
    return false;
    
    template<class T, class U> constexpr bool operator>(const optional<T>& x, const U& v);
    

    -13- Constraints: U is not a specialization of optional. The expression *x > v is well-formed and its result is convertible to bool.

    -14- Effects: Equivalent to: return x.has_value() ? *x > v : false;

    if (x.has_value())
      return *x > v;
    return false;
    
    template<class T, class U> constexpr bool operator>(const T& v, const optional<U>& x);
    

    -15- Constraints: T is not a specialization of optional. The expression v > *x is well-formed and its result is convertible to bool.

    -16- Effects: Equivalent to: return x.has_value() ? v > *x : true;

    if (x.has_value())
      return v > *x;
    return true;
    
    template<class T, class U> constexpr bool operator<=(const optional<T>& x, const U& v);
    

    -17- Constraints: U is not a specialization of optional. The expression *x <= v is well-formed and its result is convertible to bool.

    -18- Effects: Equivalent to: return x.has_value() ? *x <= v : true;

    if (x.has_value())
      return *x <= v;
    return true;
    
    template<class T, class U> constexpr bool operator<=(const T& v, const optional<U>& x);
    

    -19- Constraints: T is not a specialization of optional. The expression v <= *x is well-formed and its result is convertible to bool.

    -20- Effects: Equivalent to: return x.has_value() ? v <= *x : false;

    if (x.has_value())
      return v <= *x;
    return false;
    
    template<class T, class U> constexpr bool operator>=(const optional<T>& x, const U& v);
    

    -21- Constraints: U is not a specialization of optional. The expression *x >= v is well-formed and its result is convertible to bool.

    -22- Effects: Equivalent to: return x.has_value() ? *x >= v : false;

    if (x.has_value())
      return *x >= v;
    return false;
    
    template<class T, class U> constexpr bool operator>=(const T& v, const optional<U>& x);
    

    -23- Constraints: T is not a specialization of optional. The expression v >= *x is well-formed and its result is convertible to bool.

    -24- Effects: Equivalent to: return x.has_value() ? v >= *x : true;

    if (x.has_value())
      return v >= *x;
    return true;
    

4371(i). Container adaptor's empty/size should be noexcept

Section: 23.6.3.1 [queue.defn], 23.6.4.1 [priqueue.overview], 23.6.6.2 [stack.defn] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2025-09-09 Last modified: 2025-10-22

Priority: Not Prioritized

View all other issues in [queue.defn].

View all issues with Tentatively NAD status.

Discussion:

C++23 container adaptors flat_meow all have noexcept size/empty members.

However, the size/empty members of other container adaptors are not mark noexcept, even though they behave the same as flat_meow that returning the size/empty of the underlying container.

It makes sense to make them noexcept as well for consistency. Although the standard doesn't explicitly say those two members of the container must not throw, the fact that all standard containers and common third-party containers mark them as unconditionally noexcept implies that it's perfectly reasonable to assume that they never will.

[2025-10-22 Reflector poll. Status changed: New → Tentatively NAD.]

General disagrement with the proposed change. Implicitly changing container requirements. We should fix flat_ adaptors instead.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 23.6.3.1 [queue.defn] as indicated:

    namespace std {
      template<class T, class Container = deque<T>>
      class queue {
      public:
        […]
        constexpr bool              empty() const noexcept { return c.empty(); }
        constexpr size_type         size()  const noexcept { return c.size(); }
        […]
      };
      […]
    }
    
  2. Modify 23.6.4.1 [priqueue.overview] as indicated:

    namespace std {
      template<class T, class Container = vector<T>,
               class Compare = less<typename Container::value_type>>
      class priority_queue {
      public:
        […]
        constexpr bool            empty() const noexcept { return c.empty(); }
        constexpr size_type       size()  const noexcept { return c.size(); }
        […]
      };
      […]
    }
    
  3. Modify 23.6.6.2 [stack.defn] as indicated:

    namespace std {
      template<class T, class Container = deque<T>>
      class stack {
      public:
        […]
        constexpr bool              empty() const noexcept { return c.empty(); }
        constexpr size_type         size()  const noexcept { return c.size(); }
        […]
      };
      […]
    }
    

4372(i). Weaken Mandates: for dynamic padding values in padded layouts

Section: 23.7.3.4.8.1 [mdspan.layout.leftpad.overview], 23.7.3.4.9.1 [mdspan.layout.rightpad.overview] Status: Tentatively Ready Submitter: Luc Grosheintz Opened: 2025-09-09 Last modified: 2025-10-17

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

Two new layouts were added to <mdspan> in C++26. Both have a template parameter size_t PaddingValue. This value is allowed to be std::dynamic_extent to signal that the padding value isn't known at compile time.

A class Mandates: element (in 23.7.3.4.8.1 [mdspan.layout.leftpad.overview] (5.2) and 23.7.3.4.9.1 [mdspan.layout.rightpad.overview] (5.2), respectively) requires (unconditionally) that

Since std::dynamic_extent is defined as size_t(-1) (in 23.7.2.1 [span.syn]) this immediately prohibits all dynamically padded layout mappings for any index_type for which:

numeric_limit<index_type>::max() < numeric_limit<size_t>::max()

One example is int on a 64-bit system.

The proposed resolution states that the modified representability Mandates: element holds for rank <= 1, even though in that case the PaddingValue has no other effect. Hence, the Mandates: element could be weakened further.

[2025-10-17; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

"This matches the wording in 23.7.3.3.1 [mdspan.extents.overview] 1.2"

Proposed resolution:

This wording is relative to N5014.

  1. Modify 23.7.3.4.8.1 [mdspan.layout.leftpad.overview] as indicated:

    -5- Mandates:

    1. (5.1) — […]

    2. (5.2) — if padding_value is not equal to dynamic_extent, then padding_value is representable as a value of type index_type.

    3. (5.3) — […]

    4. (5.4) — […]

  2. Modify 23.7.3.4.9.1 [mdspan.layout.rightpad.overview] as indicated:

    -5- Mandates:

    1. (5.1) — […]

    2. (5.2) — if padding_value is not equal to dynamic_extent, then padding_value is representable as a value of type index_type.

    3. (5.3) — […]

    4. (5.4) — […]


4375(i). std::simd::bit_ceil should not be noexcept

Section: 29.10.8.15 [simd.bit] Status: Tentatively Ready Submitter: Matthias Kretz Opened: 2025-08-29 Last modified: 2025-10-22

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

std::simd::bit_ceil is declared 'noexcept' in 29.10.3 [simd.syn] and 29.10.8.15 [simd.bit]. But

  1. std::bit_ceil is not 'noexcept' (22.11.2 [bit.syn] and 22.11.5 [bit.pow.two]) and

  2. std::simd::bit_ceil has a precondition.

[2025-10-22; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 29.10.3 [simd.syn], header <simd> synopsis, as indicated:

    namespace std {
      […]
      // 29.10.8.15 [simd.bit], bit manipulation
      template<simd-vec-type V> constexpr V byteswap(const V& v) noexcept;
      template<simd-vec-type V> constexpr V bit_ceil(const V& v) noexcept;
      template<simd-vec-type V> constexpr V bit_floor(const V& v) noexcept;  
      […]
    }
    
  2. Modify 29.10.8.15 [simd.bit] as indicated:

    template<simd-vec-type V> constexpr V bit_ceil(const V& v) noexcept;
    

    -3- Constraints: The type V::value_type is an unsigned integer type (6.9.2 [basic.fundamental]).

    -4- Preconditions: […]

    […]


4377(i). Misleading note about lock-free property of std::atomic_ref

Section: 32.5.7.2 [atomics.ref.ops] Status: Tentatively Ready Submitter: Brian Bi Opened: 2025-09-15 Last modified: 2025-10-20

Priority: Not Prioritized

View other active issues in [atomics.ref.ops].

View all other issues in [atomics.ref.ops].

View all issues with Tentatively Ready status.

Discussion:

Note 1 to 32.5.7.2 [atomics.ref.ops] states:

Hardware could require an object referenced by an atomic_ref to have stricter alignment (6.8.3 [basic.align]) than other objects of type T. Further, whether operations on an atomic_ref are lock-free could depend on the alignment of the referenced object. For example, lock-free operations on std::complex<double> could be supported only if aligned to 2*alignof(double).

By using the word "Further", the note misleadingly implies that required_alignment may need to be greater than alignof(T) even before considering lock freedom, i.e., that std::atomic_ref<T> may be completely unimplementable on given hardware if the stricter alignment requirement is not met. However, that can never be true because falling back to a lock-based implementation is always possible.

The note could also be misinterpreted to imply that even though an object may be aligned to required_alignment and thus referenceable by an atomic_ref, operations could still fail to be lock-free because there is a stricter alignment requirement that the object does not meet. Such an interpretation is, however, at odds with p4.

The example given by the note is also confusing in that it does not necessarily demonstrate a situation in which std::atomic_ref<T>::required_alignment is greater than alignof(T).

In conclusion, this note appears to be a convoluted way of saying that, in order to ensure that operations on atomic_ref<T> are lock-free, the implementation may define required_alignment to a value greater than alignof(T). The note should be modified to say this much more clearly.

[2025-10-20; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll. Also ask SG1 to take a look.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 32.5.7.2 [atomics.ref.ops] as indicated:

    static constexpr size_t required_alignment;
    

    -1- The alignment required for an object to be referenced by an atomic reference, which is at least alignof(T).

    -2- [Note 1: Hardware could require an object referenced by an atomic_ref to have stricter alignment (6.8.3 [basic.align]) than other objects of type T. Further, whether operations on an atomic_ref are lock-free could depend on the alignment of the referenced object. For example, lock-free operations on std::complex<double> could be supported only if aligned to 2*alignof(double)An implementation can choose to define atomic_ref<T>::required_alignment to a value greater than alignof(T) in order to ensure that operations on all objects of type atomic_ref<T> are lock-free. — end note]


4382(i). The simd::basic_mask(bool) overload needs to be more constrained

Section: 29.10.9.2 [simd.mask.ctor] Status: Tentatively Ready Submitter: Matthias Kretz Opened: 2025-09-24 Last modified: 2025-10-27

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

29.10.9.2 [simd.mask.ctor] defines the overloads basic_mask(bool) and basic_mask(unsigned_integral auto). This leads to the following pitfall:

auto g0() {
  unsigned short k = 0xf;
  return simd::mask<float, 8>(k); // mov eax, 15
}

auto g1() {
  unsigned short k = 0xf;
  return simd::mask<float, 8>(k >> 1); // mov eax, -1 ⚠️
}

auto g2() {
  unsigned int k = 0xf;
  return simd::mask<float, 8>(k >> 1); // mov eax, 7
}

In g1, k is promoted to int, shifted and then passed to the mask constructor. Instead of failing, int(0x7) is converted to bool and the mask thus initialized to all true.

Also consider:

  1. simd::mask<float>(true_type());

  2. unsigned_integral<bool> is true => same_as<bool> auto instead of 'bool' makes the overload set ambiguous

  3. float is convertible to bool, thus simd::mask<float>(1.f) continues to compile

Previous resolution [SUPERSEDED]:

This wording is relative to N5014.

  1. Modify 29.10.9.1 [simd.mask.overview], class template basic_mask synopsis, as indicated:

    namespace std::simd {
      template<size_t Bytes, class Abi> class basic_mask {
      public:
        […]
        
        constexpr basic_mask() noexcept = default;
        
        // 29.10.9.2 [simd.mask.ctor], basic_mask constructors
        constexpr explicit basic_mask(value_type) noexcept;
        template<size_t UBytes, class UAbi>
          constexpr explicit basic_mask(const basic_mask<UBytes, UAbi>&) noexcept;
        template<class G>
          constexpr explicit basic_mask(G&& gen) noexcept;
        constexpr basic_mask(const bitset<size()>& b) noexcept;
        constexpr explicit basic_mask(unsigned_integral auto val) noexcept;
        basic_mask(signed_integral auto) = delete;
        
        […]
      };
    }
    

[2025-10-06; Matthias Kretz improves wording after reflector discussion]

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 29.10.9.1 [simd.mask.overview], class template basic_mask synopsis, as indicated:

    namespace std::simd {
      template<size_t Bytes, class Abi> class basic_mask {
      public:
        […]
        
        constexpr basic_mask() noexcept = default;
        
        // 29.10.9.2 [simd.mask.ctor], basic_mask constructors
        constexpr explicit basic_mask(same_as<value_type> auto) noexcept;
        template<size_t UBytes, class UAbi>
          constexpr explicit basic_mask(const basic_mask<UBytes, UAbi>&) noexcept;
        template<class G>
          constexpr explicit basic_mask(G&& gen) noexcept;
        template<same_as<bitset<size()>> T>
          constexpr basic_mask(const Tbitset<size()>& b) noexcept;
        template<unsigned_integral T> requires (!same_as<T, value_type>)
          constexpr explicit basic_mask(Tunsigned_integral auto val) noexcept;
        
        […]
      };
    }
    
  2. Modify 29.10.9.2 [simd.mask.ctor] as indicated:

    constexpr explicit basic_mask(same_as<value_type> auto x) noexcept;
    

    -1- Effects: Initializes each element with x.

    […]
    template<same_as<bitset<size()>> T>
      constexpr basic_mask(const Tbitset<size()>& b) noexcept;
    

    -7- Effects: Initializes the ith element with b[i] for all i in the range [0, size()).

    template<unsigned_integral T> requires (!same_as<T, value_type>)
      constexpr explicit basic_mask(Tunsigned_integral auto val) noexcept;
    

    -8- Effects: Initializes the first M elements to the corresponding bit values in val, […]


4384(i). flat_set::erase(iterator) is underconstrained

Section: 23.6.11.2 [flat.set.defn], 23.6.12.2 [flat.multiset.defn] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2025-09-25 Last modified: 2025-10-21

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

This is a follow-up of LWG 3704(i) since we now have flat_set and flat_multiset.

[2025-10-21; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 23.6.11.2 [flat.set.defn] as indicated:

    iterator erase(iterator position) requires (!same_as<iterator, const_iterator>);
    iterator erase(const_iterator position);
    
  2. Modify 23.6.12.2 [flat.multiset.defn] as indicated:

    iterator erase(iterator position) requires (!same_as<iterator, const_iterator>);
    iterator erase(const_iterator position);
    

4391(i). Ambiguities of simd::basic_vec constructor

Section: 29.10.7.2 [simd.ctor] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2025-09-29 Last modified: 2025-10-17

Priority: Not Prioritized

View other active issues in [simd.ctor].

View all other issues in [simd.ctor].

View all issues with Tentatively NAD status.

Discussion:

The broadcasting, generator-based, and range constructors of simd::basic_vec all take a single argument, and their constraints are not mutually exclusive.

This means that when a type satisfies both characteristics, such as a range that can be converted to a value_type, this will lead to ambiguity:

#include <simd>

struct S {
  operator double() const;       // basic_vec(U&& value)
  
  double operator()(int) const;  // basic_vec(G&& gen)

  double* begin() const;         // basic_vec(R&& r, flags<Flags...> = {});
  double* end() const;
  constexpr static int size() { return 2; }
};

int main() {
  std::simd::vec<double> simd(S{}); // error: call of overloaded 'basic_simd(S)' is ambiguous
}

Do we need more constraints, similar to the one in string_view(R&& r) that requires R not to be convertible to const char*, to make the above work, i.e., only invoke the broadcasting constructor?

[2025-10-17; Reflector poll. Status changed: New → Tentatively NAD.]

Users of such types should do disambiguation explicitly, basic_vec should not guess what they mean.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 29.10.7.2 [simd.ctor] as indicated:

    template<class G> constexpr explicit basic_vec(G&& gen);
    

    -8- Let Fromi denote the type decltype(gen(integral_constant<simd-size-type, i>())).

    -9- Constraints:

    1. (9.?) — constructible_from<value_type, G> is false.

    2. (9.?) — Fromi satisfies convertible_to<value_type> for all i in the range of [0, size()). In addition, for all i in the range of [0, size()), if Fromi is an arithmetic type, conversion from Fromi to value_type is value-preserving.

    […]
    template<class R, class... Flags>
      constexpr basic_vec(R&& r, flags<Flags...> = {});
    template<class R, class... Flags>
      constexpr basic_vec(R&& r, const mask_type& mask, flags<Flags...> = {});
    

    -12- Let mask be mask_type(true) for the overload with no mask parameter.

    -13- Constraints:

    1. (13.1) — R models ranges::contiguous_range and ranges::sized_range,

    2. (13.2) — ranges::size(r) is a constant expression, and

    3. (13.3) — ranges::size(r) is equal to size().,

    4. (13.?) — constructible_from<value_type, R> is false, and

    5. (13.?) — r(integral_constant<simd-size-type, 0>()) is not a valid expression.


4398(i). enable_nonlocking_formatter_optimization should be disabled for container adaptors

Section: 23.6.2 [queue.syn], 23.6.5 [stack.syn] Status: Tentatively Ready Submitter: Tomasz Kamiński Opened: 2025-10-02 Last modified: 2025-10-17

Priority: 2

View all issues with Tentatively Ready status.

Discussion:

As the standard currently defines formatters for queue, prioriy_queue, and stack enable_nonlocking_formatter_optimization is specialized to true for these adaptors per 28.5.6.4 [format.formatter.spec] p3:

Unless specified otherwise, for each type T for which a formatter specialization is provided by the library, each of the headers provides the following specialization:

template<> inline constexpr bool enable_nonlocking_formatter_optimization<T> = true;

However, formatting an adaptor requires formatting of the underlying range in terms of ranges::ref_view, and we disable the nonlocking_optimizations for all ranges, including ranges::ref_view.

This problem does not occur for the flat_set, flat_map adaptors, which are also ranges, but unlike stack etc. they do not have a specialized formatter. They use the formatter specialization for ranges and we already disable the optimization for that formatter.

The proposed wording has recently been implemented in gcc's libstdc++.

[2025-10-14; Reflector poll]

Set priority to 2 after reflector poll.

This is a duplicate of LWG 4146(i), with a different proposed resolution.

[2025-10-17; Reflector poll]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 23.6.2 [queue.syn], header <queue> synopsis, as indicated:

    […]
    // 23.6.13 [container.adaptors.format], formatter specialization for queue
    template<class charT, class T, formattable<charT> Container>
      struct formatter<queue<T, Container>, charT>;
      
    template<class T, class Container>
      constexpr bool enable_nonlocking_formatter_optimization<queue<T, Container>> = false;
    
    // 23.6.4 [priority.queue], class template priority_queue
    template<class T, class Container = vector<T>,
             class Compare = less<typename Container::value_type>>
      class priority_queue;
    […]
    // 23.6.13 [container.adaptors.format], formatter specialization for priority_queue
    template<class charT, class T, formattable<charT> Container, class Compare>
      struct formatter<priority_queue<T, Container, Compare>, charT>;
      
    template<class T, class Container, class Compare>
      constexpr bool enable_nonlocking_formatter_optimization<priority_queue<T, Container, Compare>> = false;
    […]
    
  2. Modify 23.6.5 [stack.syn], header <stack> synopsis, as indicated:

    […]
    
    // 23.6.13 [container.adaptors.format], formatter specialization for stack
    template<class charT, class T, formattable<charT> Container>
      struct formatter<stack<T, Container>, charT>;
    
    template<class T, class Container>
      constexpr bool enable_nonlocking_formatter_optimization<stack<T, Container>> = false;
    
    […]
    

4399(i). enable_nonlocking_formatter_optimization for pair and tuple needs remove_cvref_t

Section: 28.5.9 [format.tuple] Status: Tentatively Ready Submitter: Tomasz Kamiński Opened: 2025-10-02 Last modified: 2025-10-17

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

The enable_nonlocking_formatter_optimization variable template is specialized only for cv-unqualified types. However, the specialization for pair and tuple does not remove the references and cv-qualifiers from the elements:

template<class... Ts> 
  constexpr bool enable_nonlocking_formatter_optimization<pair-or-tuple<Ts...>> = 
    (enable_nonlocking_formatter_optimization<Ts> && ...);

As consequence pair<const std::string, int> or pair<const std::string&, int&> (map and flat_map reference types) will not use unbuffered prints.

The proposed wording has recently been implemented in gcc's libstdc++.

[2025-10-17; Reflector poll]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 28.5.9 [format.tuple] as indicated:

    -1- For each of pair and tuple, the library provides the following formatter specialization where pair-or-tuple is the name of the template:

    namespace std {
      […]
      
      template<class... Ts> 
        constexpr bool enable_nonlocking_formatter_optimization<pair-or-tuple<Ts...>> = 
          (enable_nonlocking_formatter_optimization<remove_cvref_t<Ts>> && ...);
    }
    

4403(i). simd::basic_vec CTAD misses difference type casting

Section: 29.10.7.2 [simd.ctor] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2025-10-04 Last modified: 2025-10-17

Priority: Not Prioritized

View other active issues in [simd.ctor].

View all other issues in [simd.ctor].

View all issues with Tentatively Ready status.

Discussion:

Currently, basic_vec can take an object r of range type R whose size is a constant expression and deduced to vec<ranges::range_value_t<R>, ranges::size(r)>.

However, such a deduced type is ill-formed when R has a an integer-class type size which cannot be implicitly converted to simd-size-type, which is a signed integer type.

It is necessary to perform difference type casting here, and the narrowing conversion will still correctly be rejected due to the constructor's constraints.

[2025-10-17; Reflector poll]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 29.10.7.2 [simd.ctor] as indicated:

    template<class R, class... Ts>
      basic_vec(R&& r, Ts...) -> see below;
    

    -17- Constraints:

    1. (17.1) — R models ranges::contiguous_range and ranges::sized_range, and

    2. (17.2) — ranges::size(r) is a constant expression.

    -18- Remarks: The deduced type is equivalent to vec<ranges::range_value_t<R>, static_cast<simd-size-type>(ranges::size(r))>


4404(i). Should span(R&&) CTAD apply P2280?

Section: 23.7.2.2.3 [span.deduct] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2025-10-04 Last modified: 2025-10-20

Priority: Not Prioritized

View all issues with Tentatively NAD status.

Discussion:

Thanks to P2280R4, simd::basic_vec's CTAD can specify template parameters directly through ranges::size:

basic_vec(R&& r, ...) -> vec<ranges::range_value_t<R>, ranges::size(r)>

However, span with similar CTAD forms do not have this automatic static size optimization applied:

span(R&&) -> span<remove_reference_t<ranges::range_reference_t<R>>>;

Do we need to do it for span?

Note that the span constructor actually requires R to be a contiguous_range and a sized_range. If it is further required that ranges::size be a constant expression, only raw array, array, span, ranges::empty_view, and ranges::single_view satisfy this requirement. Given that span already has CTAD for raw arrays and arrays, this improvement is not significant, but it still seems worthwhile as a compile-time optimization for certain user-defined types or in some specialized cases (demo):

#include <array>
#include <ranges>

constexpr std::size_t N = 42;

auto to_span(auto& r) { 
  static_assert(std::ranges::size(r) == N); // ok after P2280
  return std::span(r);
}

std::array<int, N> a;
auto s1 = to_span(a);
static_assert(std::same_as<decltype(s1), std::span<int, N>>);

auto r = std::array<int, N>{} | std::views::as_const; // as_const_view<owning_view<array<int, N>>>
auto s2 = to_span(r);
static_assert(std::same_as<decltype(s2), std::span<const int, N>>); // fire, ok after this PR

[2025-10-20 Reflector poll. Status changed: NAD → Tentatively NAD.]

This is breaking change, and we need a paper with analysis.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 23.7.2.2.1 [span.overview] as indicated:

    namespace std {
      template<class ElementType, size_t Extent = dynamic_extent>
      class span {
        […]
      };
      […]
      template<class R>
        span(R&& r) -> see belowspan<remove_reference_t<ranges::range_reference_t<R>>>;
    }
    
  2. Modify 23.7.2.2.3 [span.deduct] as indicated:

    template<class R>
      span(R&& r) -> see belowspan<remove_reference_t<ranges::range_reference_t<R>>>;
    

    -2- Constraints: R satisfies ranges::contiguous_range.

    -?- Remarks: Let T denote the type remove_reference_t<ranges::range_reference_t<R>>. The deduced type is equivalent to

    1. (?.1) — span<T, static_cast<size_t>(ranges::size(r))> if R satisfies ranges::sized_range and ranges::size(r) is a constant expression.

    2. (?.2) — span<T> otherwise.


4407(i). constexpr-wrapper-like needs remove_cvref_t in simd::basic_vec constructor

Section: 29.10.7.2 [simd.ctor] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2025-10-05 Last modified: 2025-10-17

Priority: Not Prioritized

View other active issues in [simd.ctor].

View all other issues in [simd.ctor].

View all issues with Tentatively Ready status.

Discussion:

decltype(From::value) would be const int& if From is a type of std::cw<42>, so the reference also needs to be removed for checking the arithmetic type.

[2025-10-17; Reflector poll]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 29.10.7.2 [simd.ctor] as indicated:

    template<class U> constexpr explicit(see below) basic_vec(U&& value) noexcept;
    

    -1- Let From denote the type remove_cvref_t<U>.

    […]

    -4- Remarks: The expression inside explicit evaluates to false if and only if U satisfies convertible_to<value_type>, and either

    1. (4.1) — From is not an arithmetic type and does not satisfy constexpr-wrapper-like,

    2. (4.2) — From is an arithmetic type and the conversion from From to value_type is value-preserving (29.10.1 [simd.general]), or

    3. (4.3) — From satisfies constexpr-wrapper-like, remove_cvref_tremove_const_t<decltype(From::value)> is an arithmetic type, and From::value is representable by value_type.


4412(i). Fix declaration of zero_element and uninit_element

Section: 29.10.3 [simd.syn] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-10-17 Last modified: 2025-10-22

Priority: Not Prioritized

View all other issues in [simd.syn].

View all issues with Tentatively Ready status.

Discussion:

Addresses US-174-282

zero_element and uninit_element should be inline and not static

[2025-10-22; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 29.10.3 [simd.syn] as indicated:

    // 29.10.8.7, permute by static index generator
    inline static constexpr simd-size-type zero_element = implementation-defined;
    inline static constexpr simd-size-type uninit_element = implementation-defined;
    

4413(i). Unused/left-over simd::alignment specialization for basic_mask

Section: 29.10.4 [simd.traits] Status: Tentatively Ready Submitter: Matthias Kretz Opened: 2025-10-15 Last modified: 2025-10-22

Priority: Not Prioritized

View all other issues in [simd.traits].

View all issues with Tentatively Ready status.

Discussion:

29.10.4 [simd.traits] describes a value member for simd::alignment<basic_mask<…>, bool>. This was used for loads and stores for masks from/to arrays of bool. However, these load/store functions were removed after bitset/unsigned_integral conversions were introduced. This left-over TS wording should be removed.

[2025-10-22; Reflector poll.]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 29.10.3 [simd.syn] as indicated:

    template<class T, class U = typename T::value_type> struct alignment { see below };
    

    -1- alignment<T, U> has a member value if and only if

    1. (1.1) — T is a specialization of basic_mask and U is bool, or

    2. (1.2) — T is a specialization of basic_vec and U is a vectorizable type.


4415(i). task::promise_type::uncaught_exception seems to be misnamed

Section: 33.13.6.5 [task.promise] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-10-17 Last modified: 2025-10-23

Priority: Not Prioritized

View other active issues in [task.promise].

View all other issues in [task.promise].

View all issues with Tentatively Ready status.

Discussion:

According to 9.6.4 [dcl.fct.def.coroutine], a function of name unhandled_exception may be called by the language mechanisms. However, std::execution::task<T, Environment>::promise_type has an uncaught_exception function instead, which won't be implicitly called.

In P3552R3, unhandled_exception was discussed, but the wording specified uncaught_exception, which looks like a mistake. Moreover, the paper didn't talk about the status of uncaught_exception in the zombie name list ([tab:zombie.names.std]). Perhaps unhandled_exception is the correct function name.

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 33.13.6.5 [task.promise] as indicated:

    namespace std::execution {
      template<class T, class Environment>
      class task<T, Environment>::promise_type {
      public:
        […]
        void uncaughtunhandled_exception();
        coroutine_handle<> unhandled_stopped();    
        […]
      };
    }
    
    […]
    void uncaughtunhandled_exception();
    

    -12- Effects: If the signature set_error_t(exception_ptr) is not an element of error_types, calls terminate() (14.6.2 [except.terminate]). Otherwise, stores current_exception() into errors.


4416(i). <meta> should include <compare>

Section: 21.4.1 [meta.syn] Status: Tentatively Ready Submitter: Jiang An Opened: 2025-10-15 Last modified: 2025-10-20

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

Some inclusions from <meta> were removed between P2996R7 and P2996R8. However, given std::meta::member_offsets has operator<=>, perhaps we should still include <compare> in <meta>. This is also noticed in P3429R1.

[2025-10-20; Reflector poll.]

Set status to Tentatively Ready after five votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 21.4.1 [meta.syn], header <meta> synopsis, as indicated:

    #include <compare> // see 17.12.1 [compare.syn]
    #include <initializer_list> // see 17.11.2 [initializer.list.syn]
    
    namespace std {
      […]
    }
    

4422(i). meta::access_context should be a consteval-only type

Section: 21.4.8 [meta.reflection.access.context] Status: Tentatively Ready Submitter: Jakub Jelinek Opened: 2025-10-20 Last modified: 2025-10-27

Priority: 2

View all issues with Tentatively Ready status.

Discussion:

The meta::access_context type is expected to contain some meta::info objects, which would make it a consteval-only type. But we don't actually specify any members, so nothing in the current specification says you can't persist one until runtime.

[2025-10-23; Reflector poll. Adjust proposed wording.]

Set priority to 2 after reflector poll.

Reflector discussion requested that 'non-aggregate' and 'consteval-only' both be put in paragraph 3, adjacent to 'structural'. Also added a drive-by editorial change to paragraph 1.

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 21.4.8 [meta.reflection.access.context] as indicated:

    -1- The class access_context class is a non-aggregate type that represents a namespace, class, or function from which queries pertaining to access rules may be performed, as well as the designating class (11.8.3 [class.access.base]), if any.

    -2- An access_context has an associated scope and designating class.

    namespace std::meta {
      struct access_context {
        access_context() = delete;
    
        consteval info scope() const;
        consteval info designating_class() const;
    
        static consteval access_context current() noexcept;
        static consteval access_context unprivileged() noexcept;
        static consteval access_context unchecked() noexcept;
        consteval access_context via(info cls) const;
      };
    }

    -3- access_context is a structural, consteval-only, non-aggregate type. Two values ac1 and ac2 of type access_context are template-argument-equivalent (13.6 [temp.type]) if ac1.scope() and ac2.scope() are template-argument-equivalent and ac1.designating_class() and ac2.designating_class() are template-argument-equivalent.


4425(i). CTAD function_ref of data member pointer should produce noexcept signature

Section: 22.10.17.6.5 [func.wrap.ref.deduct] Status: Tentatively Ready Submitter: Tomasz Kamiński Opened: 2025-10-20 Last modified: 2025-10-21

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

Addresses PL-005.

For data member pointer M C::* dmp, template argument deduction in form std::function_ref fr(std::nontype<dmp>, x) results in std::function_ref<M&()>, that returns a reference to designated member. As accessing a data member can never throw exception and function_ref support noexcept qualifed function types, the deduction guide should be updated to produce noexcept-qualified signature.

[2025-10-21; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 22.10.17.6.5 [func.wrap.ref.deduct] as indicated:

    template<class F>
    function_ref(nontype_t<f>, T&&) -> function_ref<see below>;
    

    Let F be decltype(f).

    -6- Constraint:

    1. (6.1) — F is of the form R(G::*)(A...) cv &opt noexcept(E) for type G, or
    2. (6.2) — F is of the form M G::* for type G and object type M, in which case let R be invoke_result_t<F, T&>, A... be an empty pack, and E be false true, or
    3. (6.3) — F is of the form R(*)(G, A...) noexcept(E) for type G.

    -7- Remarks: The deduced type is function_ref<R(A...) noexcept(E)>.


4426(i). Clarify what meta::reflect_constant_string considers a string literal

Section: 21.4.15 [meta.reflection.array] Status: Tentatively Ready Submitter: Jakub Jelinek Opened: 2025-10-21 Last modified: 2025-10-27

Priority: Not Prioritized

View other active issues in [meta.reflection.array].

View all other issues in [meta.reflection.array].

View all issues with Tentatively Ready status.

Discussion:

meta::reflect_constant_string says:

Let V be the pack of values of type CharT whose elements are the corresponding elements of r, except that if r refers to a string literal object, then V does not include the trailing null terminator of r.
It's unclear how the implementation should decide whether r refers to a string literal. If R models contiguous_iterator, should it use meta::is_string_literal(ranges::data(r))? Should it omit the '\0' from string_view("abc", 3)?

Also, "null terminator" is only defined in 27.4.3.1 [basic.string.general] and not used for string literal objects (5.13.5 [lex.string]).

[2025-10-23; Reflector poll.]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 21.4.15 [meta.reflection.array] as indicated:

    template<ranges::input_range R>
      consteval info reflect_constant_string(R&& r);
    

    -2- Let CharT be ranges::range_value_t<R>.

    -3- Mandates: CharT is one of char, wchar_t, char8_t, char16_t, char32_t.

    Let V be the pack of values of type CharT whose elements are the corresponding elements of r, except that if r refers is a reference to a string literal object, then V does not include the trailing null terminator terminating u+0000 null character of r.