Revised 2024-04-19 at 18:42:34 UTC

Tentative Issues


2457(i). std::begin() and std::end() do not support multi-dimensional arrays correctly

Section: 25.7 [iterator.range] Status: Tentatively NAD Submitter: Janez Žemva Opened: 2014-11-16 Last modified: 2023-04-06

Priority: 3

View other active issues in [iterator.range].

View all other issues in [iterator.range].

View all issues with Tentatively NAD status.

Discussion:

The following code:

#include <algorithm>
#include <iterator>
#include <iostream>
#include <cassert>

int main() 
{
  int a[2][3][4] = { { { 1,  2,  3,  4}, { 5,  6,  7,  8}, { 9, 10, 11, 12} },
                     { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } };
  int b[2][3][4];

  assert(std::distance(std::begin(a), std::end(a)) == 2 * 3 * 4);
  std::copy(std::begin(a), std::end(a), std::begin(b));
  std::copy(std::begin(b), std::end(b), std::ostream_iterator<int>(std::cout, ","));
}

does not compile.

A possible way to remedy this would be to add the following overloads of begin, end, rbegin, and rend to 25.7 [iterator.range], relying on recursive evaluation:

namespace std {

  template <typename T, size_t M, size_t N>
  constexpr remove_all_extents_t<T>*
  begin(T (&array)[M][N])
  {
    return begin(*array);
  }
  
  template <typename T, size_t M, size_t N>
  constexpr remove_all_extents_t<T>*
  end(T (&array)[M][N])
  {
    return end(array[M - 1]);
  }

  template <typename T, size_t M, size_t N>
  reverse_iterator<remove_all_extents_t<T>*>
  rbegin(T (&array)[M][N])
  {
    return decltype(rbegin(array))(end(array[M - 1]));
  }
  
  template <typename T, size_t M, size_t N>
  reverse_iterator<remove_all_extents_t<T>*>
  rend(T (&array)[M][N])
  {
    return decltype(rend(array))(begin(*array));
  }

}

[2023-04-06; LWG reflector poll in November 2021]

Changed to Tentatively NAD after 12 votes in favour. Use views::join or mdspan instead.

Proposed resolution:


3635(i). Add __cpp_lib_deduction_guides to feature test macros

Section: 17.3.2 [version.syn] Status: Tentatively NAD Submitter: Konstantin Varlamov Opened: 2021-11-09 Last modified: 2023-05-24

Priority: 3

View other active issues in [version.syn].

View all other issues in [version.syn].

View all issues with Tentatively NAD status.

Discussion:

P0433R2, the proposal for adding deduction guides to the standard library, contained a recommendation to use __cpp_lib_deduction_guides as a feature test macro. However, it appears that this feature test macro has been accidentally omitted from the Standard when the paper was applied and probably needs to be added back.

Previous resolution [SUPERSEDED]:

This wording is relative to N4901.

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

    […]
    #define __cpp_lib_coroutine          201902L  // also in <coroutine>
    #define __cpp_lib_deduction_guides   201703L
      // also in <deque>, <forward_list>, <list>, <map>, <queue>, <set>, <stack>,
      // <unordered_map>, <unordered_set>, <vector>
    #define __cpp_lib_destroying_delete  201806L  // also in <new>
    […]
    

[2021-11-16; Konstantin Varlamov comments and improves wording]

One potential topic of discussion is whether the new feature test macro needs to be defined in every library header that contains an explicit deduction guide. While this would be consistent with the current approach, no other macro is associated with such a large set of headers (20 headers in total, whereas the current record-holder is __cpp_lib_nonmember_container_access with 12 headers). For this reason, it should be considered whether perhaps the new macro should only be defined in <version> (which would, however, make it an outlier). The proposed wording currently contains an exhaustive list (note that the deduction guides for <mutex> were removed by LWG 2981).

[2022-01-30; Reflector poll]

Set priority to 3 after reflector poll. Several votes for NAD as it's too late to be useful, and code which needs to be portable to pre-CTAD compilers can just not use CTAD.

[2023-04-21; Reflector poll for 'Tentatively NAD']

[ "We keep changing the deduction guides, and different libraries might be conformant in some headers and not others. The status cannot be represented by a single number." ]

Proposed resolution:

This wording is relative to N4901.

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

    […]
    #define __cpp_lib_coroutine          201902L  // also in <coroutine>
    #define __cpp_lib_deduction_guides   201703L
      // also in <array>, <deque>, <forward_list>, <functional>, <list>, <map>,
      // <memory>, <optional>, <queue>, <regex>, <scoped_allocator>, <set>, <stack>,
      // <string>, <tuple>, <unordered_map>, <unordered_set>, <utility>, <valarray>,
      // <vector>
    #define __cpp_lib_destroying_delete  201806L  // also in <new>
    […]
    

3714(i). Non-single-argument constructors for range adaptors should not be explicit

Section: 26.7.26.2 [range.zip.transform.view], 26.7.28.2 [range.adjacent.transform.view], 26.7.29.2 [range.chunk.view.input], 26.7.29.6 [range.chunk.view.fwd], 26.7.30.2 [range.slide.view], 26.7.31.2 [range.chunk.by.view] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2022-06-10 Last modified: 2024-01-29

Priority: 4

View all issues with Tentatively NAD status.

Discussion:

All C++20 range adaptors' non-single-argument constructors are not declared as explicit, which makes the following initialization well-formed:

std::vector v{42};
std::ranges::take_view r1 = {v, 1};
std::ranges::filter_view r2 = {v, [](int) { return true; }};

However, the non-single-argument constructors of C++23 range adaptors, except for join_with_view, are all explicit, which makes us no longer able to

std::ranges::chunk_view r1 = {v, 1}; // ill-formed
std::ranges::chunk_by_view r2 = {v, [](int, int) { return true; }}; // ill-formed

This seems unnecessary since I don't see the observable benefit of preventing this. In the standard, non-single-argument constructors are rarely specified as explicit unless it is really undesirable, I think the above initialization is what the user expects since it's clearly intentional, and I don't see any good reason to reject it from C++23.

[2022-06-11; Daniel comments]

Another possible candidate could be 26.7.11.3 [range.take.while.sentinel]'s

constexpr explicit sentinel(sentinel_t<Base> end, const Pred* pred);

[2022-06-21; Reflector poll]

Set priority to 4 after reflector poll. Send to LEWG.

[2023-01-24; LEWG in Kona]

Use alternative approach in P2711 instead.

Proposed resolution:

This wording is relative to N4910.

  1. Modify 26.7.26.2 [range.zip.transform.view] as indicated:

    namespace std::ranges {
      template<copy_constructible F, input_range... Views>
        requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<F> &&
                  regular_invocable<F&, range_reference_t<Views>...> &&
                  can-reference<invoke_result_t<F&, range_reference_t<Views>...>>
      class zip_transform_view : public view_interface<zip_transform_view<F, Views...>> {
        copyable-box<F> fun_;                   // exposition only
        zip_view<Views...> zip_;                // exposition only
        […]
      public:
        zip_transform_view() = default;
    
        constexpr explicit zip_transform_view(F fun, Views... views);
        […]
      };
      […]
    }
    
    constexpr explicit zip_transform_view(F fun, Views... views);
    

    -1- Effects: Initializes fun_ with std::move(fun) and zip_ with std::move(views)....

  2. Modify 26.7.28.2 [range.adjacent.transform.view] as indicated:

    namespace std::ranges {
      template<forward_range V, copy_constructible F, size_t N>
        requires view<V> && (N > 0) && is_object_v<F> &&
                 regular_invocable<F&, REPEAT(range_reference_t<V>, N)...> &&
                 can-reference<invoke_result_t<F&, REPEAT(range_reference_t<V>, N)...>>
      class adjacent_transform_view : public view_interface<adjacent_transform_view<V, F, N>> {
        copyable-box<F> fun_;                       // exposition only
        adjacent_view<V, N> inner_;                 // exposition only
        […]
      public:
        adjacent_transform_view() = default;
        constexpr explicit adjacent_transform_view(V base, F fun);
        […]
      };
      […]
    }
    
    constexpr explicit adjacent_transform_view(V base, F fun);
    

    -1- Effects: Initializes fun_ with std::move(fun) and inner_ with std::move(base).

  3. Modify 26.7.29.2 [range.chunk.view.input] as indicated:

    namespace std::ranges {
      […]
      template<view V>
        requires input_range<V>
      class chunk_view : public view_interface<chunk_view<V>> {
        V base_ = V();                                        // exposition only
        range_difference_t<V> n_ = 0;                         // exposition only
        […]
      public:
        chunk_view() requires default_initializable<V> = default;
        constexpr explicit chunk_view(V base, range_difference_t<V> n);
        […]
      };
      […]
    }
    
    constexpr explicit chunk_view(V base, range_difference_t<V> n);
    

    -1- Preconditions: n > 0 is true.

    -2- Effects: Initializes base_ with std::move(base) and n_ with n.

  4. Modify 26.7.29.6 [range.chunk.view.fwd] as indicated:

    namespace std::ranges {
      template<view V>
        requires forward_range<V>
      class chunk_view<V> : public view_interface<chunk_view<V>> {
        V base_ = V();                      // exposition only
        range_difference_t<V> n_ = 0;       // exposition only
        […]
      public:
        chunk_view() requires default_initializable<V> = default;
        constexpr explicit chunk_view(V base, range_difference_t<V> n);
        […]
      };
    }
    
    constexpr explicit chunk_view(V base, range_difference_t<V> n);
    

    -1- Preconditions: n > 0 is true.

    -2- Effects: Initializes base_ with std::move(base) and n_ with n.

  5. Modify 26.7.30.2 [range.slide.view] as indicated:

    namespace std::ranges {
      […]
      template<forward_range V>
        requires view<V>
      class slide_view : public view_interface<slide_view<V>> {
        V base_ = V();                      // exposition only
        range_difference_t<V> n_ = 0;       // exposition only
        […]
      public:
        slide_view() requires default_initializable<V> = default;
        constexpr explicit slide_view(V base, range_difference_t<V> n);
        […]
      };
      […]
    }
    
    constexpr explicit slide_view(V base, range_difference_t<V> n);
    

    -1- Effects: Initializes base_ with std::move(base) and n_ with n.

  6. Modify 26.7.31.2 [range.chunk.by.view] as indicated:

    namespace std::ranges {
      template<forward_range V, indirect_binary_predicate<iterator_t<V>, iterator_t<V>> Pred>
        requires view<V> && is_object_v<Pred>
      class chunk_by_view : public view_interface<chunk_by_view<V, Pred>> {
        V base_ = V();                                // exposition only
        copyable-box<Pred> pred_ = Pred();            // exposition only
        […]
      public:
        chunk_by_view() requires default_initializable<V> && default_initializable<Pred> = default;
        constexpr explicit chunk_by_view(V base, Pred pred);
        […]
      };
      […]
    }
    
    constexpr explicit chunk_by_view(V base, Pred pred);
    

    -1- Effects: Initializes base_ with std::move(base) and pred_ with std::move(pred).


3901(i). Is uses-allocator construction of a cv-qualified object type still well-formed after LWG 3870?

Section: 20.2.8 [allocator.uses] Status: Tentatively NAD Submitter: Jiang An Opened: 2023-03-05 Last modified: 2023-03-22

Priority: Not Prioritized

View all issues with Tentatively NAD status.

Discussion:

LWG 3870 made std::construct_at unable to create an object of a cv-qualified type, which affects std::uninitialized_construct_using_allocator. However, uses-allocator construction is currently not required to be equivalent to some call to std::uninitialized_construct_using_allocator, which possibly implies that uses-allocator construction of a cv-qualified type may still be required to be well-formed.

Should we make such construction ill-formed?

[2023-03-22; Reflector poll]

Set status to Tentatively NAD.

Not all uses-allocator construction is done using construct_at. std::tuple<const T>(allocator_arg, alloc) does uses-allocator construction of a const type, so we can't make it ill-formed.

Proposed resolution:


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

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

Priority: Not Prioritized

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.

Proposed resolution:

This wording is relative to N4944.

  1. Modify the class synopsis in 26.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 26.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], 26.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, 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 26.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. 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 26.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 26.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 26.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 26.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: [...]


3930(i). Simplify type trait wording

Section: 99 [meta] Status: Tentatively NAD Submitter: Alisdair Meredith Opened: 2023-05-01 Last modified: 2023-06-01

Priority: Not Prioritized

View other active issues in [meta].

View all other issues in [meta].

View all issues with Tentatively NAD status.

Discussion:

There are many traits that have a requirement that they are instantiated only if "T shall be a complete type, cv void, or an array of unknown bound."

Breaking down what this means, by supporting cv-void and arrays of unknown bound (almost) the only remaining type-category is incomplete class types.

The remaining edge case is incomplete enumerations, but they are required to have a known fixed-base, so act as complete types, they can be copied, assigned, etc., without knowing the names of their enumerators.

Hence, I suggest clearer wording would be: "T shall not be an incomplete class type."

This is easier to understand, as we do not need to mentally enumerate every type against a list to check it qualifies; it is a simpler test for the library to check if we were to mandate these restrictions.

There are a very small number of traits with subtly different wording, where incomplete unions are supported, or arrays of unknown bound are not a concern due to invoking remove_all_extents first. The bulk of the changes can be made to traits with only the precise wording above though, and then we can review whether any of the remaining restrictions deserve a wording update of their own.

[2023-06-01; Reflector poll]

Set status to Tentatively NAD after four votes in favour during reflector poll, including a request to withdraw the issue from the submitter.

Incomplete enumeration types are found within the enum-specifier of an enum without a fixed underlying type:


enum E {
    A = sizeof(E) // error, E is incomplete at this point
};
and we definitely can't provide an underlying type for this case.

Proposed resolution:

This wording is relative to N4944.

  1. Throughout 99 [meta] replace all occurrences of

    T shall be a complete type, cv void, or an array of unknown bound.

    by

    T shall not be an incomplete class type.


3936(i). Are implementations allowed to deprecate components not in [depr]?

Section: 99 [depr] Status: Tentatively NAD Submitter: Jiang An Opened: 2023-05-22 Last modified: 2023-06-01

Priority: Not Prioritized

View all issues with Tentatively NAD status.

Discussion:

D.1 [depr.general]/2 allows implementations to apply the deprecated attribute to deprecated components. However, there doesn't seem to be any wording disallowing applying the deprecated attribute to non-deprecated components.

Is it intended to allow implementations to deprecate every library component as they want? If so, should we turn the allowance into "Recommended practice" and move it to somewhere in 16.4 [requirements]?

There doesn't seem to be wording which formally recommends applying deprecated attribute to deprecated components either.

[2023-06-01; Reflector poll]

Set status to Tentatively NAD after nine votes in favour during reflector poll. Let implementations decide when to apply these attributes.

Proposed resolution:

This wording is relative to N4950.

[Drafting Note: There are two mutually exclusive proposed resolutions, depending on whether it is allowed to deprecate components not in 99 [depr].

Option A:

  1. Insert a paragraph at the end of 16.4.2.2 [contents]:

    -?- Recommended practice: Implementations should not apply the deprecated attribute (9.12.5 [dcl.attr.deprecated]) to library entities that are not specified in 99 [depr]. Implementations should apply the deprecated attribute to library entities specified in 99 [depr] whenever possible.

Option B:

  1. Insert two paragraphs at the end of 16.4.2.2 [contents]:

    -?- Implementations shall not apply the deprecated attribute (9.12.5 [dcl.attr.deprecated]) to library entities that are not specified in 99 [depr].

    -?- Recommended practice: Implementations should apply the deprecated attribute to library entities specified in 99 [depr] whenever possible.


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

Section: 26.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 26.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: 33.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 33.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 33.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: 26.7.2 [range.adaptor.object] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-08-22 Last modified: 2024-01-29

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 stdlibc++ 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 26.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: 26.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 26.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.


3996(i). projected<I, identity> should just be I

Section: 25.3.6.4 [projected] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-10-12 Last modified: 2023-11-03

Priority: Not Prioritized

View all other issues in [projected].

View all issues with Tentatively NAD status.

Discussion:

Currently, projected is a wrapper of the implementation type regardless of whether Proj is identity.

Since identity always returns a reference, this prevents projected<I, identity> from fully emulating the properties of the original iterator when its reference is a prvalue.

Such non-equivalence may lead to unexpected behavior in some cases (demo):

#include <algorithm>
#include <ranges>
#include <iostream>

int main() {
  auto outer = std::views::iota(0, 5)
             | std::views::transform([](int i) {
                 return std::views::single(i) | std::views::filter([](int) { return true; });
               });
  
  for (auto&& inner : outer)
    for (auto&& elem : inner)
      std::cout << elem << " "; // 0 1 2 3 4 
  
  std::ranges::for_each(
    outer,
    [](auto&& inner) {
      // error: passing 'const filter_view' as 'this' argument discards qualifiers
      for (auto&& elem : inner)
        std::cout << elem << " ";
    });
}

In the above example, ranges::for_each requires indirect_unary_predicate<Pred, projected<I, identity>> which ultimately requires invocable<Pred&, iter_common_reference_t<projected<I, identity>>>.

According to the current wording, the reference and indirect value type of projected<I, identity> are filter_view&& and filter_view& respectively, which causes its common reference to be eventually calculated as const filter_view&. Since the former is not const-iterable, this results in a hard error during instantiation because const begin is called unexpectedly in an unconstrained lambda.

It seems like having projected<I, identity> just be I is a more appropriate choice, which makes the concept checking really specific to I rather than a potentially incomplete iterator wrapper.

[2023-11-03; Reflector poll]

NAD. P2997 solves this, and more. "Applying the projection does in fact materialize prvalues, so this is just lying unless we special-case identity everywhere."

Proposed resolution:

This wording is relative to N4958.

  1. Modify 25.3.6.4 [projected] as indicated:

    -1- Class template projected is used to constrain algorithms that accept callable objects and projections (3.43 [defns.projection]). It combines an indirectly_readable type I and a callable object type Proj into a new indirectly_readable type whose reference type is the result of applying Proj to the iter_reference_t of I.

    namespace std {
      template<class I, class Proj>
      struct projected-impl {                               // exposition only
        struct type {                                       // exposition only
          using value_type = remove_cvref_t<indirect_result_t<Proj&, I>>;
          using difference_type = iter_difference_t<I>;     // present only if I
                                                            // models weakly_incrementable
          indirect_result_t<Proj&, I> operator*() const;    // not defined
        };
      };
    
      template<indirectly_readable I, indirectly_regular_unary_invocable<I> Proj>
        using projected = conditional_t<is_same_v<Proj, identity>, I, typename projected-impl<I, Proj>::type>;
    }
    

4003(i). view_interface::back is overconstrained

Section: 26.5.3 [view.interface] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-10-28 Last modified: 2023-11-07

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 26.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 26.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: 26.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 26.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;


4056(i). The effects of std::swap are under-specified

Section: 22.2.2 [utility.swap] Status: Tentatively NAD Submitter: Jan Schultke Opened: 2024-02-28 Last modified: 2024-03-15

Priority: Not Prioritized

View other active issues in [utility.swap].

View all other issues in [utility.swap].

View all issues with Tentatively NAD status.

Discussion:

Subclause 22.2.2 [utility.swap] describes the effect of std::swap as follows:

Effects: Exchanges values stored in two locations.

This description is extremely vague. A possible implementation which complies with this wording is:

template<class T>
constexpr void swap(T&, T&) noexcept(/* ... */)
{
    int __x = 0, __y = 0;
    int __z = __x;
    __x = __y;
    __y = __z;
}

This exchanges values stored in two locations; namely in the locations of two objects with automatic storage duration within swap. Since this has no observable effect and complies, it is also valid to implement swap as follows:

template<class T>
constexpr void swap(T&, T&) noexcept(/* ... */) { }

Furthermore, there is implementation divergence. libc++ uses direct-initialization to construct a temporary T, but libstdc++ uses copy-initialization. For most types, this hopefully calls the same constructor, however, this is not universally true. The standard should specify in more detail what is meant to happen.

[2024-03-15; Reflector poll]

Set status to Tentatively NAD Editorial after reflector poll.

Cpp17MoveConstructible require direct-init and copy-init to be semantically equivalent, so the different implementation techniques can only be observed by types which fail to meet the function's preconditions.

Replace the unusual "stored in two locations" wording editorially.

Proposed resolution:

This wording is relative to N4971.

  1. Modify 22.2.2 [utility.swap] as indicated:

    template<class T>
      constexpr void swap(T& a, T& b) noexcept(see below);
    

    -1- Constraints: is_move_constructible_v<T> is true and is_move_assignable_v<T> is true.

    -2-Preconditions: Type T meets the Cpp17MoveConstructible (Table 31) and Cpp17MoveAssignable (Table 33) requirements.

    -3- Effects: Exchanges values stored in two locations.Equivalent to:

    auto t(std::move(a));
    a = std::move(b);
    b = std::move(t);
    

    -4- Remarks: The exception specification is equivalent to: […]