Revised 2024-11-14 at 11:34:10 UTC

Tentative Issues


3216(i). Rebinding the allocator before calling construct/destroy in allocate_shared

Section: 20.3.2.2.7 [util.smartptr.shared.create] Status: Tentatively Ready Submitter: Billy O'Neal III Opened: 2019-06-11 Last modified: 2024-10-02

Priority: 3

View other active issues in [util.smartptr.shared.create].

View all other issues in [util.smartptr.shared.create].

View all issues with Tentatively Ready status.

Discussion:

The new allocate_shared wording says we need to rebind the allocator back to T's type before we can call construct or destroy, but this is suboptimal (might make extra unnecessary allocator copies), and is inconsistent with the containers' behavior, which call allocator construct on whatever T they want. (For example, std::list<T, alloc<T>> rebinds to alloc<_ListNode<T>>, but calls construct(T*) without rebinding back)

It seems like we should be consistent with the containers and not require a rebind here. PR would look something like this, relative to N4810; I'm still not super happy with this wording because it looks like it might be saying a copy of the allocator must be made we would like to avoid…

[2019-07 Issue Prioritization]

Priority to 3 after discussion on the reflector.

Previous resolution [SUPERSEDED]:

This wording is relative to N4810.

  1. Modify 20.3.2.2.7 [util.smartptr.shared.create] as indicated:

    [Drafting note: The edits to change pv to pu were suggested by Jonathan Wakely (thanks!). This wording also has the remove_cv_t fixes specified by LWG 3210(i) — if that change is rejected some of those have to be stripped here.]

    template<class T, ...>
      shared_ptr<T> make_shared(args);
    template<class T, class A, ...>
      shared_ptr<T> allocate_shared(const A& a, args);
    template<class T, ...>
      shared_ptr<T> make_shared_default_init(args);
    template<class T, class A, ...>
      shared_ptr<T> allocate_shared_default_init(const A& a, args);
    

    -2- Requires: […]

    […]

    -7- Remarks:

    1. (7.1) — […]

    2. […]

    3. (7.5) — When a (sub)object of a non-array type U is specified to have an initial value of v, or U(l...), where l... is a list of constructor arguments, allocate_shared shall initialize this (sub)object via the expression

      1. (7.5.1) — allocator_traits<A2>::construct(a2, pvu, v) or

      2. (7.5.2) — allocator_traits<A2>::construct(a2, pvu, l...)

      respectively, where pvu is a pointer of type remove_cv_t<U>* pointsing to storage suitable to hold an object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

    4. (7.6) — […]

    5. (7.7) — When a (sub)object of non-array type U is specified to have a default initial value, allocate_shared shall initializes this (sub)object via the expression allocator_traits<A2>::construct(a2, pvu), where pvu is a pointer of type remove_cv_t<U>* pointsing to storage suitable to hold an object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

    6. […]

    7. (7.12) — When a (sub)object of non-array type U that was initialized by allocate_shared is to be destroyed, it is destroyed via the expression allocator_traits<A2>::destroy(a2, pvu) where pvu is a pointer of type remove_cv_t<U>* pointsing to that object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

[2024-08-23; Jonathan provides updated wording]

make_shared_default_init and allocate_shared_default_init were renamed by P1973R1 so this needs a rebase. The edit to (7.11) is just for consistency, so that pv is always void* and pu is remove_cv_t<U>*. Accepting this proposed resolution would also resolve issue 3210(i).

[2024-10-02; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 20.3.2.2.7 [util.smartptr.shared.create] as indicated:

    template<class T, ...>
      shared_ptr<T> make_shared(args);
    template<class T, class A, ...>
      shared_ptr<T> allocate_shared(const A& a, args);
    template<class T, ...>
      shared_ptr<T> make_shared_for_overwrite(args);
    template<class T, class A, ...>
      shared_ptr<T> allocate_shared_for_overwrite(const A& a, args);
    

    -2- Preconditions: […]

    […]

    -7- Remarks:

    1. (7.1) — […]

    2. […]

    3. (7.5) — When a (sub)object of a non-array type U is specified to have an initial value of v, or U(l...), where l... is a list of constructor arguments, allocate_shared shall initialize this (sub)object via the expression

      1. (7.5.1) — allocator_traits<A2>::construct(a2, pvu, v) or

      2. (7.5.2) — allocator_traits<A2>::construct(a2, pvu, l...)

      respectively, where pvu is a pointer of type remove_cv_t<U>* pointsing to storage suitable to hold an object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

    4. (7.6) — […]

    5. (7.7) — When a (sub)object of non-array type U is specified to have a default initial value, allocate_shared shall initializes this (sub)object via the expression allocator_traits<A2>::construct(a2, pvu), where pvu is a pointer of type remove_cv_t<U>* pointsing to storage suitable to hold an object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

    6. […]

    7. [Drafting note: Issue 4024(i) will add make_shared_for_overwrite and allocate_shared_for_overwrite to (7.11) but that doesn't conflict with this next edit.]

      (7.11) — When a (sub)object of non-array type U that was initialized by make_shared is to be destroyed, it is destroyed via the expression pvu->~U() where pvu points to that object of type U.

    8. (7.12) — When a (sub)object of non-array type U that was initialized by allocate_shared is to be destroyed, it is destroyed via the expression allocator_traits<A2>::destroy(a2, pvu) where pvu is a pointer of type remove_cv_t<U>* pointsing to that object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.


3886(i). Monad mo' problems

Section: 22.5.3.1 [optional.optional.general], 22.8.6.1 [expected.object.general] Status: Tentatively Ready Submitter: Casey Carter Opened: 2023-02-13 Last modified: 2024-09-19

Priority: 3

View other active issues in [optional.optional.general].

View all other issues in [optional.optional.general].

View all issues with Tentatively Ready status.

Discussion:

While implementing P2505R5 "Monadic Functions for std::expected" we found it odd that the template type parameter for the assignment operator that accepts an argument by forwarding reference is defaulted, but the template type parameter for value_or is not. For consistency, it would seem that meow.value_or(woof) should accept the same arguments woof as does meow = woof, even when those arguments are braced-initializers.

That said, it would be peculiar to default the template type parameter of value_or to T instead of remove_cv_t<T>. For expected<const vector<int>, int> meow{unexpect, 42};, for example, meow.value_or({1, 2, 3}) would create a temporary const vector<int> for the argument and return a copy of that argument. Were the default template argument instead remove_cv_t<T>, meow.value_or({1, 2, 3}) could move construct its return value from the argument vector<int>. For the same reason, the constructor that accepts a forwarding reference with a default template argument of T should default that argument to remove_cv_t<T>.

For consistency, it would be best to default the template argument of the perfect-forwarding construct, perfect-forwarding assignment operator, and value_or to remove_cv_t<T>. Since all of the arguments presented apply equally to optional, we believe optional should be changed consistently with expected. MSVCSTL has prototyped these changes successfully.

[2023-03-22; Reflector poll]

Set priority to 3 after reflector poll.

[2024-09-18; Reflector poll]

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

Proposed resolution:

This wording is relative to N4928.

  1. Modify 22.5.3.1 [optional.optional.general] as indicated:

    namespace std {
      template<class T>
      class optional {
      public:
        […]
        template<class U = remove_cv_t<T>>
          constexpr explicit(see below) optional(U&&);
        […]
        template<class U = remove_cv_t<T>> constexpr optional& operator=(U&&);
        […]
        template<class U = remove_cv_t<T>> constexpr T value_or(U&&) const &;
        template<class U = remove_cv_t<T>> constexpr T value_or(U&&) &&;
        […]
      };
      […]
    }
    
  2. Modify 22.5.3.2 [optional.ctor] as indicated:

    template<class U = remove_cv_t<T>> constexpr explicit(see below) optional(U&& v);
    

    -23- Constraints: […]

  3. Modify 22.5.3.4 [optional.assign] as indicated:

    template<class U = remove_cv_t<T>> constexpr optional& operator=(U&& v);
    

    -12- Constraints: […]

  4. Modify 22.5.3.7 [optional.observe] as indicated:

    template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) const &;
    

    -15- Mandates: […]

    […]

    template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) &&;
    

    -17- Mandates: […]

  5. Modify 22.8.6.1 [expected.object.general] as indicated:

    namespace std {
      template<class T, class E>
      class expected {
      public:
        […]
        template<class U = remove_cv_t<T>>
          constexpr explicit(see below) expected(U&& v);
        […]
        template<class U = remove_cv_t<T>> constexpr expected& operator=(U&&);
        […]
        template<class U = remove_cv_t<T>> constexpr T value_or(U&&) const &;
        template<class U = remove_cv_t<T>> constexpr T value_or(U&&) &&;
        […]
      };
      […]
    }
    
  6. Modify 22.8.6.2 [expected.object.cons] as indicated:

    template<class U = remove_cv_t<T>>
      constexpr explicit(!is_convertible_v<U, T>) expected(U&& v);
    

    -23- Constraints: […]

  7. Modify 22.8.6.4 [expected.object.assign] as indicated:

    template<class U = remove_cv_t<T>>
      constexpr expected& operator=(U&& v);
    

    -9- Constraints: […]

  8. Modify 22.8.6.6 [expected.object.obs] as indicated:

    template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) const &;
    

    -16- Mandates: […]

    […]

    template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) &&;
    

    -18- Mandates: […]


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;


4084(i). std::fixed ignores std::uppercase

Section: 28.3.4.3.3.3 [facet.num.put.virtuals] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2024-04-30 Last modified: 2024-09-19

Priority: 3

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

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

View all issues with Tentatively Ready status.

Discussion:

In Table 114 – Floating-point conversions [tab:facet.num.put.fp] we specify that a floating-point value should be printed as if by %f when (flags & floatfield) == fixed. This ignores whether uppercase is also set in flags, meaning there is no way to use the conversion specifier %F that was added to printf in C99.

That's fine for finite values, because 1.23 in fixed format has no exponent character and no hex digits that would need to use uppercase. But %f and %F are not equivalent for non-finite values, because %F prints "NAN" and "INF" (or "INFINITY"). It seems there is no way to print "NAN" or "INF" using std::num_put.

Libstdc++ and MSVC print "inf" for the following code, but libc++ prints "INF" which I think is non-conforming:

    std::cout << std::uppercase << std::fixed << std::numeric_limits<double>::infinity();

The libc++ behaviour seems more useful and less surprising.

[2024-05-08; Reflector poll]

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

[2024-09-17; LEWG mailing list vote]

Set status to Open after LEWG approved the proposed change.

[2024-09-19; Reflector poll]

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

Proposed resolution:

This wording is relative to N4981.

  1. Modify 28.3.4.3.3.3 [facet.num.put.virtuals] as indicated:

    Table 114 – Floating-point conversions [tab:facet.num.put.fp]
    State stdio equivalent
    floatfield == ios_base::fixed && !uppercase %f
    floatfield == ios_base::fixed %F
    floatfield == ios_base::scientific && !uppercase %e
    floatfield == ios_base::scientific %E
    floatfield == (ios_base::fixed | ios_base::scientific)` && !uppercase %a
    floatfield == (ios_base::fixed | ios_base::scientific) %A
    !uppercase %g
    otherwise %G

4088(i). println ignores the locale imbued in std::ostream

Section: 31.7.6.3.5 [ostream.formatted.print] Status: Tentatively Ready Submitter: Jens Maurer Opened: 2024-04-30 Last modified: 2024-10-03

Priority: Not Prioritized

View other active issues in [ostream.formatted.print].

View all other issues in [ostream.formatted.print].

View all issues with Tentatively Ready status.

Discussion:

31.7.6.3.5 [ostream.formatted.print] specifies that std::print uses the locale imbued in the std::ostream& argument for formatting, by using this equivalence:

vformat(os.getloc(), fmt, args);

(in the vformat_(non)unicode delegation).

However, std::println ignores the std::ostream's locale for its locale-dependent formatting:

print(os, "{}\n", format(fmt, std::forward<Args>(args)...));

This is inconsistent.

[2024-10-03; Reflector poll]

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

Proposed resolution:

This wording is relative to N4981.

  1. Modify 31.7.6.3.5 [ostream.formatted.print] as indicated:

    template<class... Args>
      void println(ostream& os, format_string<Args...> fmt, Args&&... args);
    

    -2- Effects: Equivalent to:

    print(os, "{}\n", format(os.getloc(), fmt, std::forward<Args>(args)...));
    

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.


4113(i). Disallow has_unique_object_representations<Incomplete[]>

Section: 21.3.5.4 [meta.unary.prop] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2024-06-25 Last modified: 2024-08-02

Priority: Not Prioritized

View other active issues in [meta.unary.prop].

View all other issues in [meta.unary.prop].

View all issues with Tentatively Ready status.

Discussion:

The type completeness requirements for has_unique_object_representations say:

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

This implies that the trait works for all arrays of unknown bound, whether the element type is complete or not. That seems to be incorrect, because has_unique_object_representations_v<Incomplete[]> is required to have the same result as has_unique_object_representations_v<Incomplete> which is ill-formed if Incomplete is an incomplete class type.

I think we need the element type to be complete to be able to give an answer. Alternatively, if the intended result for an array of unknown bound is false (maybe because there can be no objects of type T[], or because we can't know that two objects declared as extern T a[]; and extern T b[]; have the same number of elements?) then the condition for the trait needs to be special-cased as false for arrays of unknown bound. The current spec is inconsistent, we can't allow arrays of unknown bound and apply the current rules to determine the trait's result.

[2024-08-02; Reflector poll]

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

Proposed resolution:

This wording is relative to N4981.

  1. Modify 21.3.5.4 [meta.unary.prop] as indicated:

    TemplateConditionPreconditions
    template<class T>
    struct has_unique_object_representations;
    For an array type T, the same result as has_unique_object_representations_v<remove_all_extents_t<T>>, otherwise see below. remove_all_extents_t<T> T shall be a complete type, or cv void, or an array of unknown bound.

    [Drafting note: We could use remove_extent_t<T> to remove just the first array dimension, because only that first one can have an unknown bound. The proposed resolution uses remove_all_extents_t<T> for consistency with the Condition column.]


4119(i). generator::promise_type::yield_value(ranges::elements_of<R, Alloc>)'s nested generator may be ill-formed

Section: 25.8.5 [coro.generator.promise] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2024-07-11 Last modified: 2024-08-02

Priority: Not Prioritized

View other active issues in [coro.generator.promise].

View all other issues in [coro.generator.promise].

View all issues with Tentatively Ready status.

Discussion:

The nested coroutine is specified to return generator<yielded, ranges::range_value_t<R>, Alloc> which can be problematic as the value type of R is really irrelevant to yielded, unnecessarily violating the generator's Mandates (demo):

#include <generator>
#include <vector>

std::generator<std::span<int>> f() {
  std::vector<int> v;
  co_yield v; // ok
}

std::generator<std::span<int>> g() {
  std::vector<std::vector<int>> v;
  co_yield std::ranges::elements_of(v); // hard error
}

This proposed resolution is to change the second template parameter from range_value_t<R> to void since that type doesn't matter to us.

[2024-08-02; Reflector poll]

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

Proposed resolution:

This wording is relative to N4986.

  1. Modify 25.8.5 [coro.generator.promise] as indicated:

    template<ranges::input_range R, class Alloc>
      requires convertible_to<ranges::range_reference_t<R>, yielded>
      auto yield_value(ranges::elements_of<R, Alloc> r);
    

    -13- Effects: Equivalent to:

    auto nested = [](allocator_arg_t, Alloc, ranges::iterator_t<R> i, ranges::sentinel_t<R> s)
      -> generator<yielded, ranges::range_value_t<R>void, Alloc> {
        for (; i != s; ++i) {
          co_yield static_cast<yielded>(*i);
        }
      };
    return yield_value(ranges::elements_of(nested(
      allocator_arg, r.allocator, ranges::begin(r.range), ranges::end(r.range))));
    
    […]

4124(i). Cannot format zoned_time with resolution coarser than seconds

Section: 30.12 [time.format] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2024-07-26 Last modified: 2024-08-02

Priority: Not Prioritized

View other active issues in [time.format].

View all other issues in [time.format].

View all issues with Tentatively Ready status.

Discussion:

The std::formatter<std::chrono::zoned_time<Duration, TimeZonePtr>> specialization calls tp.get_local_time() for the object it passes to its base class' format function. But get_local_time() does not return a local_time<Duration>, it returns local_time<common_type_t<Duration, seconds>>. The base class' format function is only defined for local_time<Duration>. That means this is ill-formed, even though the static assert passes:

using namespace std::chrono;
  static_assert( std::formattable<zoned_time<minutes>, char> );
  zoned_time<minutes> zt;
  (void) std::format("{}", zt); // error: cannot convert local_time<seconds> to local_time<minutes>

Additionally, it's not specified what output you should get for:

std::format("{}", local_time_format(zt.get_local_time()));
30.12 [time.format] p7 says it's formatted as if by streaming to an ostringstream, but there is no operator<< for local-time-format-t. Presumably it should give the same result as operator<< for a zoned_time, i.e. "{:L%F %T %Z}" with padding adjustments etc.

The proposed resolution below has been implemented in libstdc++.

[2024-08-02; Reflector poll]

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

Proposed resolution:

This wording is relative to N4986.

  1. Modify 30.12 [time.format] as indicated:

    template<classDuration, class charT>
      struct formatter<chrono::local-time-format-t<Duration>, charT>;
    

    -17- Let f be a locale-time-format-t<Duration> object passed to formatter::format.

    -18- Remarks: If the chrono-specs is omitted, the result is equivalent to using %F %T %Z as the chrono-specs. If %Z is used, it is replaced with *f.abbrev if f.abbrev is not a null pointer value. If %Z is used and f.abbrev is a null pointer value, an exception of type format_error is thrown. If %z (or a modified variant of %z) is used, it is formatted with the value of *f.offset_sec if f.offset_sec is not a null pointer value. If %z (or a modified variant of %z) is used and f.offset_sec is a null pointer value, then an exception of type format_error is thrown.

      template<class Duration, class TimeZonePtr, class charT>
      struct formatter<chrono::zoned_time<Duration, TimeZonePtr>, charT>
          : formatter<chrono::local-time-format-t<common_type_t<Duration, seconds>>, charT> {
        template<class FormatContext>
          typename FormatContext::iterator
          format(const chrono::zoned_time<Duration, TimeZonePtr>& tp, FormatContext& ctx) const;
      };
    
    template<class FormatContext>
      typename FormatContext::iterator
        format(const chrono::zoned_time<Duration, TimeZonePtr>& tp, FormatContext& ctx) const;
    

    -19- Effects: Equivalent to:

    sys_info info = tp.get_info();
    return formatter<chrono::local-time-format-t<common_type_t<Duration, seconds>>, charT>::
             format({tp.get_local_time(), &info.abbrev, &info.offset}, ctx);
    


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

Section: 17.3.2 [version.syn] Status: Tentatively Ready Submitter: Jiang An Opened: 2024-07-24 Last modified: 2024-08-02

Priority: 2

View other active issues in [version.syn].

View all other issues in [version.syn].

View all issues with Tentatively Ready status.

Discussion:

Currently (N4986), it's a bit weird in 17.3.2 [version.syn] that some feature-test macros are not marked freestanding, despite the indicated features being fully freestanding. The freestanding status seems sometimes implicitly covered by "also in" headers that are mostly or all freestanding, but sometimes not.

I think it's more consistent to ensure feature-test macros for fully freestanding features are also freestanding.

[2024-08-02; Reflector poll]

Set priority to 2 and set status to Tentatively Ready after seven votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N4986.

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

    [Drafting note: <charconv> is not fully freestanding, but all functions made constexpr by P2291R3 are furtherly made freestanding by P2338R4. ]

    […]
    #define __cpp_lib_common_reference                  202302L // freestanding, also in <type_traits>
    #define __cpp_lib_common_reference_wrapper          202302L // freestanding, also in <functional>
    […]                                                                              
    #define __cpp_lib_constexpr_charconv                202207L // freestanding, also in <charconv>
    […]                                                                              
    #define __cpp_lib_coroutine                         201902L // freestanding, also in <coroutine>
    […]                                                                              
    #define __cpp_lib_is_implicit_lifetime              202302L // freestanding, also in <type_traits>
    […]                                                                              
    #define __cpp_lib_is_virtual_base_of                202406L // freestanding, also in <type_traits>
    […]                                                                              
    #define __cpp_lib_is_within_lifetime                202306L // freestanding, also in <type_traits>
    […]                                                                              
    #define __cpp_lib_mdspan                            202406L // freestanding, also in <mdspan>
    […]                                                                              
    #define __cpp_lib_ratio                             202306L // freestanding, also in <ratio>
    […]                                                                              
    #define __cpp_lib_span_initializer_list             202311L // freestanding, also in <span>
    […]                                                                              
    #define __cpp_lib_submdspan                         202403L // freestanding, also in <mdspan>
    […]                                                                              
    #define __cpp_lib_to_array                          201907L // freestanding, also in <array>
    […]
    

4135(i). The helper lambda of std::erase for list should specify return type as bool

Section: 23.3.7.7 [forward.list.erasure], 23.3.9.6 [list.erasure] Status: Tentatively Ready Submitter: Hewill Kang Opened: 2024-08-07 Last modified: 2024-08-21

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

std::erase for list is specified to return erase_if(c, [&](auto& elem) { return elem == value; }). However, the template parameter Predicate of erase_if only requires that the type of decltype(pred(...)) satisfies boolean-testable, i.e., the return type of elem == value is not necessarily bool.

This means it's worth explicitly specifying the lambda's return type as bool to avoid some pedantic cases (demo):

#include <list>

struct Bool {
  Bool(const Bool&) = delete;
  operator bool() const;
};

struct Int {
  Bool& operator==(Int) const;
};

int main() {
  std::list<Int> l;
  std::erase(l, Int{}); // unnecessary hard error
}

[2024-08-21; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 23.3.7.7 [forward.list.erasure] as indicated:

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

    -1- Effects: Equivalent to: return erase_if(c, [&](const auto& elem) -> bool { return elem == value; });

  2. Modify 23.3.9.6 [list.erasure] as indicated:

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

    -1- Effects: Equivalent to: return erase_if(c, [&](const auto& elem) -> bool { return elem == value; });


4140(i). Useless default constructors for bit reference types

Section: 22.9.2.1 [template.bitset.general], 23.3.12.1 [vector.bool.pspc] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2024-08-21 Last modified: 2024-09-18

Priority: Not Prioritized

View all issues with Tentatively Ready status.

Discussion:

The standard shows a private default constructor for bitset<N>::reference but does not define its semantics, and nothing in the spec refers to it. It was present in C++98, then in C++11 it got noexcept added to it, and in C++23 it was made constexpr by P2417R2. That's quite a lot of churn for an unusuable member function with no definition.

In libstdc++ it's declared as private, but never defined. In libc++ it doesn't exist at all. In MSVC it is private and defined (and presumably used somewhere). There's no reason for the standard to declare it. Implementers can define it as private if they want to, or not. The spec doesn't need to say anything for that to be true. We can also remove the friend declaration, because implementers know how to do that too.

I suspect it was added as private originally so that it didn't look like reference should have an implicitly-defined default constructor, which would have been the case in previous standards with no other constructors declared. However, C++20 added reference(const reference&) = default; which suppresses the implicit default constructor, so declaring the default constructor as private is now unnecessary.

Jiang An pointed out in an editorial pull request that vector<bool, Alloc>::reference has exactly the same issue.

[2024-09-18; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 22.9.2.1 [template.bitset.general] as indicated:

    namespace std {
      template<size_t N> class bitset {
      public:
        // bit reference
        class reference {
          friend class bitset;
          constexpr reference() noexcept;
    
        public:
          constexpr reference(const reference&) = default;
          constexpr ~reference();
          constexpr reference& operator=(bool x) noexcept;            // for b[i] = x;
          constexpr reference& operator=(const reference&) noexcept;  // for b[i] = b[j];
          constexpr bool operator~() const noexcept;                  // flips the bit
          constexpr operator bool() const noexcept;                   // for x = b[i];
          constexpr reference& flip() noexcept;                       // for b[i].flip();
        };
    
  2. Modify 23.3.12.1 [vector.bool.pspc], vector<bool, Allocator> synopsis, as indicated:

    namespace std {
      template<class Allocator>
      class vector<bool, Allocator> {
      public:
        // types
        […]
        // bit reference
        class reference {
          friend class vector;
          constexpr reference() noexcept;
    
        public:
          constexpr reference(const reference&) = default;
          constexpr ~reference();
          constexpr operator bool() const noexcept;
          constexpr reference& operator=(bool x) noexcept;
          constexpr reference& operator=(const reference& x) noexcept;
          constexpr const reference& operator=(bool x) const noexcept;
          constexpr void flip() noexcept;   // flips the bit
        };
    

4141(i). Improve prohibitions on "additional storage"

Section: 22.5.3.1 [optional.optional.general], 22.6.3.1 [variant.variant.general], 22.8.6.1 [expected.object.general], 22.8.7.1 [expected.void.general] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2024-08-22 Last modified: 2024-09-18

Priority: Not Prioritized

View other active issues in [optional.optional.general].

View all other issues in [optional.optional.general].

View all issues with Tentatively Ready status.

Discussion:

This issue was split out from issue 4015(i).

optional, variant and expected all use similar wording to require their contained value to be a subobject, rather than dynamically allocated and referred to by a pointer, e.g.

When an instance of optional<T> contains a value, it means that an object of type T, referred to as the optional object’s contained value, is allocated within the storage of the optional object. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate its contained value.

During the LWG reviews of P2300 in St. Louis, concerns were raised about the form of this wording and whether it's normatively meaningful. Except for the special case of standard-layout class types, the standard has very few requirements on where or how storage for subobjects is allocated. The library should not be trying to dictate more than the language guarantees. It would be better to refer to wording from 6.7.2 [intro.object] such as subobject, provides storage, or nested within. Any of these terms would provide the desired properties, without using different (and possibly inconsistent) terminology.

Using an array of bytes to provide storage for the contained value would make it tricky to meet the constexpr requirements of types like optional. This means in practice, the most restrictive of these terms, subobject, is probably accurate and the only plausible implementation strategy. However, I don't see any reason to outlaw other implementation strategies that might be possible in future (say, with a constexpr type cast, or non-standard compiler-specific instrinics). For this reason, the proposed resolution below uses nested within, which provides the desired guarantee without imposing additional restrictions on implementations.

[2024-09-18; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 22.5.3.1 [optional.optional.general] as indicated:

    [Drafting note: This edit modifies the same paragraph as issue 4015(i), but that other issue intentionally doesn't touch the affected sentence here (except for removing the italics on "contained value"). The intention is that the merge conflict can be resolved in the obvious way: "An optional object's contained value is nested within (6.7.2 [intro.object]) the optional object."]

    -1- Any instance of optional<T> at any given time either contains a value or does not contain a value. When an instance of optional<T> contains a value, it means that an object of type T, referred to as the optional object's contained value, is allocated within the storage of nested within (6.7.2 [intro.object]) the optional object. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate its contained value. When an object of type optional<T> is contextually converted to bool, the conversion returns true if the object contains a value; otherwise the conversion returns false.

  2. Modify 22.6.3.1 [variant.variant.general] as indicated:

    -1- Any instance of variant at any given time either holds a value of one of its alternative types or holds no value. When an instance of variant holds a value of alternative type T, it means that a value of type T, referred to as the variant object's contained value, is allocated within the storage of nested within (6.7.2 [intro.object]) the variant object. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate the contained value.

  3. Modify 22.8.6.1 [expected.object.general] as indicated:

    -1- Any object of type expected<T, E> either contains a value of type T or a value of type E within its own storage nested within (6.7.2 [intro.object]) it. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate the object of type T or the object of type E. Member has_val indicates whether the expected<T, E> object contains an object of type T.

  4. Modify 22.8.7.1 [expected.void.general] as indicated:

    -1- Any object of type expected<T, E> either represents a value of type T, or contains a value of type E within its own storage nested within (6.7.2 [intro.object]) it. Implementations are not permitted to use additional storage, such as dynamic memory, to allocate the object of type E. Member has_val indicates whether the expected<T, E> represents a value of type T.


4142(i). format_parse_context::check_dynamic_spec should require at least one type

Section: 28.5.6.6 [format.parse.ctx] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2024-08-28 Last modified: 2024-09-18

Priority: Not Prioritized

View all other issues in [format.parse.ctx].

View all issues with Tentatively Ready status.

Discussion:

The Mandates: conditions for format_parse_context::check_dynamic_spec are:

-14- Mandates: The types in Ts... are unique. Each type in Ts... is one of bool, char_type, int, unsigned int, long long int, unsigned long long int, float, double, long double, const char_type*, basic_string_view<char_type>, or const void*.

There seems to be no reason to allow Ts to be an empty pack, that's not useful. There is no valid arg-id value that can be passed to it if the list of types is empty, since arg(n) will never be one of the types in an empty pack. So it's never a constant expression if the pack is empty.

[2024-09-18; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 28.5.6.6 [format.parse.ctx] as indicated:

    template<class... Ts>
      constexpr void check_dynamic_spec(size_t id) noexcept;
    

    -14- Mandates: sizeof...(Ts) ≥ 1. The types in Ts... are unique. Each type in Ts... is one of bool, char_type, int, unsigned int, long long int, unsigned long long int, float, double, long double, const char_type*, basic_string_view<char_type>, or const void*.

    -15- Remarks: A call to this function is a core constant expression only if:

    1. (15.1) — id < num_args_ is true and
    2. (15.2) — the type of the corresponding format argument (after conversion to basic_format_arg<Context>) is one of the types in Ts....


4144(i). Disallow unique_ptr<T&, D>

Section: 20.3.1.3.1 [unique.ptr.single.general] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2024-08-30 Last modified: 2024-11-13

Priority: Not Prioritized

View all other issues in [unique.ptr.single.general].

View all issues with Tentatively Ready status.

Discussion:

It seems that we currently allow nonsensical specializations of unique_ptr such as unique_ptr<int&, D> and unique_ptr<void()const, D> (a custom deleter that defines D::pointer is needed, because otherwise the pointer type would default to invalid types like int&* or void(*)()const). There seems to be no reason to support these "unique pointer to reference" and "unique pointer to abominable function type" specializations, or any specialization for a type that you couldn't form a raw pointer to.

Prior to C++17, the major library implementations rejected such specializations as a side effect of the constraints for the unique_ptr(auto_ptr<U>&&) constructor being defined in terms of is_convertible<U*, T*>. This meant that overload resolution for any constructor of unique_ptr would attempt to form the type T* and fail if that was invalid. With the removal of auto_ptr in C++17, that constructor was removed and now unique_ptr<int&, D> can be instantiated (assuming any zombie definition of auto_ptr is not enabled by the library). This wasn't intentional, but just an accident caused by not explicitly forbidding such types.

Discussion on the LWG reflector led to near-unanimous support for explicitly disallowing these specializations for non-pointable types.

[2024-11-13; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 20.3.1.3.1 [unique.ptr.single.general] as indicated:

    -?- A program that instantiates the definition of unique_ptr<T, D> is ill-formed if T* is an invalid type.
    [Note: This prevents the intantiation of specializations such as unique_ptr<T&, D> and unique_ptr<int() const, D>. — end note]

    -1- The default type for the template parameter D is default_delete. A client-supplied template argument D shall be a function object type (22.10 [function.objects]), lvalue reference to function, or lvalue reference to function object type for which, given a value d of type D and a value ptr of type unique_ptr<T, D>::pointer, the expression d(ptr) is valid and has the effect of disposing of the pointer as appropriate for that deleter.

    -2- If the deleter’s type D is not a reference type, D shall meet the Cpp17Destructible requirements (Table 35).

    -3- If the qualified-id remove_reference_t<D>::pointer is valid and denotes a type (13.10.3 [temp.deduct]), then unique_ptr<T, D>::pointer shall be a synonym for remove_reference_t<D>::pointer. Otherwise unique_ptr<T, D>::pointer shall be a synonym for element_type*. The type unique_ptr<T, D>::pointer shall meet the Cpp17NullablePointer requirements (Table 36).

    -4- [Example 1:  Given an allocator type X (16.4.4.6.1 [allocator.requirements.general]) and letting A be a synonym for allocator_traits<X>, the types A::pointer, A::const_pointer, A::void_pointer, and A::const_void_pointer may be used as unique_ptr<T, D>::pointer. — end example]


4147(i). Precondition on inplace_vector::emplace

Section: 23.2.4 [sequence.reqmts] Status: Tentatively Ready Submitter: Arthur O'Dwyer Opened: 2024-08-26 Last modified: 2024-09-18

Priority: Not Prioritized

View other active issues in [sequence.reqmts].

View all other issues in [sequence.reqmts].

View all issues with Tentatively Ready status.

Discussion:

Inserting into the middle of an inplace_vector, just like inserting into the middle of a vector or deque, requires that we construct the new element out-of-line, shift down the trailing elements (Cpp17MoveAssignable), and then move-construct the new element into place (Cpp17MoveInsertable). P0843R14 failed to make this change, but it should have.

[2024-09-18; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 23.2.4 [sequence.reqmts] as indicated:

    a.emplace(p, args)
    

    -19- Result: iterator.

    -20- Preconditions: T is Cpp17EmplaceConstructible into X from args. For vector, inplace_vector, and deque, T is also Cpp17MoveInsertable into X and Cpp17MoveAssignable.

    -21- Effects: Inserts an object of type T constructed with std::forward<Args>(args)... before p.

    [Note 1: args can directly or indirectly refer to a value in a. — end note]

    -22- Returns: An iterator that points to the new element constructed from args into a.


4148(i). unique_ptr::operator* should not allow dangling references

Section: 20.3.1.3.5 [unique.ptr.single.observers] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2024-09-02 Last modified: 2024-09-18

Priority: Not Prioritized

View other active issues in [unique.ptr.single.observers].

View all other issues in [unique.ptr.single.observers].

View all issues with Tentatively Ready status.

Discussion:

If unique_ptr<T,D>::element_type* and D::pointer are not the same type, it's possible for operator*() to return a dangling reference that has undefined behaviour.


  struct deleter {
    using pointer = long*;
    void operator()(pointer) const {}
  };
  long l = 0;
  std::unique_ptr<const int, deleter> p(&l);
  int i = *p; // undefined

We should make this case ill-formed.

[2024-09-18; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 20.3.1.3.5 [unique.ptr.single.observers] as indicated:

    constexpr add_lvalue_reference_t<T> operator*() const noexcept(noexcept(*declval<pointer>()));
    

    -?- Mandates: reference_converts_from_temporary_v<add_lvalue_reference_t<T>, decltype(*declval<pointer>())> is false.

    -1- Preconditions: get() != nullptr is true.

    -2- Returns: *get().


4153(i). Fix extra "-1" for philox_engine::max()

Section: 29.5.4.5 [rand.eng.philox] Status: Tentatively Ready Submitter: Ruslan Arutyunyan Opened: 2024-09-18 Last modified: 2024-10-02

Priority: Not Prioritized

View other active issues in [rand.eng.philox].

View all other issues in [rand.eng.philox].

View all issues with Tentatively Ready status.

Discussion:

There is a typo in philox_engine wording that makes "-1" two times instead of one for max() method. The reason for that typo is that the wording was originally inspired by mersenne_twister_engine but after getting feedback that what is written in the philox_engine synopsis is not C++ code, the authors introduced the m variable (as in subtract_with_carry_engine) but forgot to remove "-1" in the m definition.

Note: after the proposed resolution below is applied the m variable could be reused in other places: basically in all places where the mod 2^w pattern appears (like subtract_with_carry_engine does). The authors don’t think it’s worth changing the rest of the wording to reuse the m variable. If somebody thinks otherwise, please provide such feedback.

[2024-10-02; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 29.5.4.5 [rand.eng.philox] as indicated:

    -1- A philox_engine random number engine produces unsigned integer random numbers in the closed interval [0, m]), where m = 2w − 1 and the template parameter w defines the range of the produced numbers.

4157(i). The resolution of LWG3465 was damaged by P2167R3

Section: 17.11.6 [cmp.alg] Status: Tentatively Ready Submitter: Jiang An Opened: 2024-09-18 Last modified: 2024-10-02

Priority: Not Prioritized

View other active issues in [cmp.alg].

View all other issues in [cmp.alg].

View all issues with Tentatively Ready status.

Discussion:

In the resolution of LWG 3465(i), F < E was required to be well-formed and implicitly convertible to bool. However, P2167R3 replaced the convertibility requirements with just "each of decltype(E == F) and decltype(E < F) models boolean-testable", which rendered the type of F < E underconstrained.

[2024-10-02; Reflector poll]

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

Proposed resolution:

This wording is relative to N4988.

  1. Modify 17.11.6 [cmp.alg] as indicated:

    (6.3) — Otherwise, if the expressions E == F, E < F, and F < E are all well-formed and each of decltype(E == F) and, decltype(E < F) , and decltype(F < E) models boolean-testable,
    
      E == F ? partial_ordering::equivalent :
      E < F  ? partial_ordering::less :
      F < E  ? partial_ordering::greater :
               partial_ordering::unordered
    
    except that E and F are evaluated only once.

4169(i). std::atomic<T>'s default constructor should be constrained

Section: 32.5.8.2 [atomics.types.operations] Status: Tentatively Ready Submitter: Giuseppe D'Angelo Opened: 2024-10-15 Last modified: 2024-11-13

Priority: Not Prioritized

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

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

View all issues with Tentatively Ready status.

Discussion:

The current wording for std::atomic's default constructor in 32.5.8.2 [atomics.types.operations] specifies:

constexpr atomic() noexcept(is_nothrow_default_constructible_v<T>);

Mandates: is_default_constructible_v<T> is true.

This wording has been added by P0883R2 for C++20, which changed std::atomic's default constructor to always value-initialize. Before, the behavior of this constructor was not well specified (this was LWG issue 2334(i)).

The usage of a Mandates element in the specification has as a consequence that std::atomic<T> is always default constructible, even when T is not. For instance:

// not default constructible:
struct NDC { NDC(int) {} };

static_assert(std::is_default_constructible<std::atomic<NDC>>); // OK

The above check is OK as per language rules, but this is user-hostile: actually using std::atomic<NDC>'s default constructor results in an error, despite the detection saying otherwise.

Given that std::atomic<T> already requires T to be complete anyhow (32.5.8.1 [atomics.types.generic.general] checks for various type properties which require completeness) it would be more appropriate to use a constraint instead, so that std::atomic<T> is default constructible if and only if T also is.

[2024-11-13; Reflector poll]

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

Proposed resolution:

This wording is relative to N4993.

  1. Modify 32.5.8.2 [atomics.types.operations] as indicated:

    [Drafting note: There is implementation divergence at the moment; libstdc++ already implements the proposed resolution and has done so for a while.]

    constexpr atomic() noexcept(is_nothrow_default_constructible_v<T>);
    

    -1- ConstraintsMandates: is_default_constructible_v<T> is true.

    -2- Effects: […]


4170(i). contiguous_iterator should require to_address(I{})

Section: 24.3.4.14 [iterator.concept.contiguous] Status: Tentatively Ready Submitter: Casey Carter Opened: 2024-11-01 Last modified: 2024-11-13

Priority: Not Prioritized

View all other issues in [iterator.concept.contiguous].

View all issues with Tentatively Ready status.

Discussion:

The design intent of the contiguous_iterator concept is that iterators can be converted to pointers denoting the same sequence of elements. This enables a common range [i, j) or counted range i + [0, n) to be processed with extremely efficient low-level C or assembly code that operates on [to_address(i), to_address(j)) (respectively to_address(i) + [0, n)).

A value-initialized iterator I{} can be used to denote the empty ranges [I{}, I{}) and I{} + [0, 0). While the existing semantic requirements of contiguous_iterator enable us to convert both dereferenceable and past-the-end iterators with to_address, converting ranges involving value-initialized iterators to pointer ranges additionally needs to_address(I{}) to be well-defined. Note that to_address is already implicitly equality-preserving for contiguous_iterator arguments. Given this additional requirement to_address(I{}) == to_address(I{}) and to_address(I{}) == to_address(I{)) + 0 both hold, so the two types of empty ranges involving value-initialized iterators convert to empty pointer ranges as desired.

[2024-11-13; Reflector poll]

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

Proposed resolution:

This wording is relative to N4993.

  1. Modify 24.3.4.14 [iterator.concept.contiguous] as indicated:

    -1- The contiguous_iterator concept provides a guarantee that the denoted elements are stored contiguously in memory.

    template<class I>
      concept contiguous_iterator =
        random_access_iterator<I> &&
        derived_from<ITER_CONCEPT(I), contiguous_iterator_tag> &&
        is_lvalue_reference_v<iter_reference_t<I>> &&
        same_as<iter_value_t<I>, remove_cvref_t<iter_reference_t<I>>> &&
        requires(const I& i) {
          { to_address(i) } -> same_as<add_pointer_t<iter_reference_t<I>>>;
        };
    

    -2- Let a and b be dereferenceable iterators and c be a non-dereferenceable iterator of type I such that b is reachable from a and c is reachable from b, and let D be iter_difference_t<I>. The type I models contiguous_iterator only if

    1. (2.1) — to_address(a) == addressof(*a),

    2. (2.2) — to_address(b) == to_address(a) + D(b - a),

    3. (2.3) — to_address(c) == to_address(a) + D(c - a),

    4. (2.?) — to_address(I{}) is well-defined,

    5. (2.4) — ranges::iter_move(a) has the same type, value category, and effects as std::move(*a), and

    6. (2.5) — if ranges::iter_swap(a, b) is well-formed, it has effects equivalent to ranges::swap(*a, *b).