Revised 2025-02-22 at 22:48:07 UTC

Tentative Issues


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.5 [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.9.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.4 [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.4 [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.


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;


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


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:


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:


4188(i). ostream::sentry destructor should handle exceptions

Section: 31.7.6.2.4 [ostream.sentry] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2025-01-14 Last modified: 2025-02-07

Priority: Not Prioritized

View other active issues in [ostream.sentry].

View all other issues in [ostream.sentry].

View all issues with Tentatively Ready status.

Discussion:

LWG 397(i) suggested changing 31.7.6.2.4 [ostream.sentry] to say that the ostream::sentry destructor doesn't throw any exceptions. That issue was closed as resolved by LWG 835(i) which included the "Throws: Nothing" change to the sentry destructor. However, that part of the resolution never seems to have been applied to the working draft. N3091 mentions applying LWG 835 for N3090 but the destructor change is missing, maybe because the paragraph for the sentry destructor had been renumbered from p17 to p4 and LWG 835 didn't show sufficient context to indicate the intended location.

The problem described in LWG 397(i) is still present: the streambuf operations can fail, and the sentry needs to handle that. The changes for LWG 835(i) ensure no exception is thrown if rdbuf()->pubsync() returns -1 on failure, but do nothing for the case where it throws an exception (the original topic of LWG 397!). Because C++11 made ~sentry implicitly noexcept, an exception from rdbuf()->pubsync() will terminate the process. That needs to be fixed.

Libstdc++ does terminate if pubsync() throws when called by ~sentry. Both MSVC and Libc++ silently swallow exceptions. It seems preferable to handle the exception and report an error, just as we do when pubsync() returns -1.

[2025-02-07; Reflector poll]

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

Proposed resolution:

This wording is relative to N5001.

  1. Modify 31.7.6.2.4 [ostream.sentry] as indicated:

    ~sentry();
    -4- If (os.flags() & ios_base::unitbuf) && !uncaught_exceptions() && os.good() is true, calls os.rdbuf()->pubsync(). If that function returns −1 or exits via an exception, sets badbit in os.rdstate() without propagating an exception.

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.


4200(i). The operation_state concept can be simplified

Section: 33.8.1 [exec.opstate.general] Status: Tentatively Ready Submitter: Eric Niebler Opened: 2025-02-03 Last modified: 2025-02-07

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

Imported from cplusplus/sender-receiver #312.

The current defn of the operation_state concept is:


template<class O>
  concept operation_state =
    derived_from<typename O::operation_state_concept, operation_state_t> &&
    is_object_v<O> &&
    requires (O& o) {
      { start(o) } noexcept;
    };
I think the is_object_v<O> constraint is not needed because the derived_from constraint has already established that O is a class type.

And start(o) is always noexcept now that start mandates the noexcept-ness of op.start().

[2025-02-07; 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 33.8.1 [exec.opstate.general] as indicated:
    
    template<class O>
      concept operation_state =
        derived_from<typename O::operation_state_concept, operation_state_t> &&
        is_object_v<O> &&
        requires (O& o) {
          { start(o) } noexcept;
        };
    

4201(i). with-await-transform::await_transform should not use a deduced return type

Section: 33.9.4 [exec.awaitable] Status: Tentatively Ready Submitter: Brian Bi Opened: 2025-02-03 Last modified: 2025-02-07

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

Imported from cplusplus/sender-receiver #309.

33.9.4 [exec.awaitable]/p5

The use of the deduced return type causes the definition of the sender's as_awaitable method to be instantiated too early, e.g., when the sender is passed to get_completion_signatures.

[Eric provides wording]

[2025-02-07; Reflector poll]

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

Proposed resolution:

This wording is relative to N5001.

  1. Modify 33.9.4 [exec.awaitable] as indicated:
    -5- Let with-await-transform be the exposition-only class template:
    
    namespace std::execution {
      template<class T, class Promise>
        concept has-as-awaitable =                                  // exposition only
          requires (T&& t, Promise& p) {
            { std::forward<T>(t).as_awaitable(p) } -> is-awaitable<Promise&>;
          };
    
      template<class Derived>
        struct with-await-transform {                               // exposition only
          template<class T>
            T&& await_transform(T&& value) noexcept {
              return std::forward<T>(value);
            }
    
          template<has-as-awaitable<Derived> T>
            decltype(auto)auto await_transform(T&& value)
              noexcept(noexcept(std::forward<T>(value).as_awaitable(declval<Derived&>())))
            -> decltype(std::forward<T>(value).as_awaitable(declval<Derived&>())) {
              return std::forward<T>(value).as_awaitable(static_cast<Derived&>(*this));
            }
        };
    }