Revised 2025-11-06 at 18:16:32 UTC
enumerate_view::iterator constructor is explicitSection: 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.
Modify the class synopsis in 25.7.24.3 [range.enumerate.iterator] as shown:
constexprexplicititerator(iterator_t<Base> current, difference_type pos); // exposition only
Modify the detailed description in 25.7.24.3 [range.enumerate.iterator] as shown:
constexprexplicititerator(iterator_t<Base> current, difference_type pos);-2- Effects: Initializes
current_withstd::move(current)andpos_withpos.
viewable_rangeSection: 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.
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
Change the definition of viewable_range in 25.4.6 [range.refinements] as indicated:
-6- The
viewable_rangeconcept specifies the requirements of arangetype 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>))));
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
as its first argument and returns a view.viewable_rangerange[…]
-8- If a range adaptor object
adaptoraccepts more than one argument, then letrangebe an expression such thatdecltype((range))models, letviewable_rangerangeargs...be arguments such thatadaptor(range, args...)is a well-formed expression as specified in the rest of subclause 25.7 [range.adaptors], and letBoundArgsbe a pack that denotesdecay_t<decltype((args))>.... The expressionadaptor(args...)produces a range adaptor closure objectfthat is a perfect forwarding call wrapper (22.10.4 [func.require]) with the following properties: [...]
ranges::to should prioritize the "reserve" branchSection: 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)...).
#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.
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.
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:
Cis a cv-unqualified class type.-2- Returns: An object of type
Cconstructed from the elements ofrin the following manner:
(2.1) — If
Cdoes not satisfyinput_rangeorconvertible_to<range_reference_t<R>, range_value_t<C>>istrue:
(2.1.1) — If
constructible_from<C, R, Args...>istrue:C(std::forward<R>(r), std::forward<Args>(args)...)(2.1.2) — Otherwise, if
constructible_from<C, from_range_t, R, Args...>istrue:C(from_range, std::forward<R>(r), std::forward<Args>(args)...)
(2.1.3) — Otherwise, if
(2.1.3.1) —common_range<R>istrue,
(2.1.3.2) — the qualified-iditerator_traits<iterator_t<R>>::iterator_categoryis valid and denotes a type that modelsderived_from<input_iterator_tag>, and
(2.1.3.3) —constructible_from<C, iterator_t<R>, sentinel_t<R>, Args...>istrue:C(ranges::begin(r), ranges::end(r), std::forward<Args>(args)...)(2.1.4) — Otherwise, if
(2.1.4.1) —
constructible_from<C, Args...>istrue, and(2.1.4.2) —
container-insertable<C, range_reference_t<R>>istrue: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));(?.?.?) — Otherwise, if
(?.?.?.?) —
common_range<R>istrue,(?.?.?.?) — the qualified-id
iterator_traits<iterator_t<R>>::iterator_categoryis valid and denotes a type that modelsderived_from<input_iterator_tag>, and(?.?.?.?) —
constructible_from<C, iterator_t<R>, sentinel_t<R>, Args...>istrue:C(ranges::begin(r), ranges::end(r), std::forward<Args>(args)...)(2.2) — Otherwise, if
input_range<range_reference_t<R>>istrue:to<C>(r | views::transform([](auto&& elem) { return to<range_value_t<C>>(std::forward<decltype(elem)>(elem)); }), std::forward<Args>(args)...);(2.3) — Otherwise, the program is ill-formed.
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:
Can the read operations in compare_exchange_strong in different two thread read the same value?
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
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.
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, whereXimmediately precedes the side effect of the read-modify-write operation in the modification order.
This wording keeps a similar utterance to 6.10.2.2 [intro.races], and it can clearly convey the meaning
that we say the value read by RWM is associated with the side effect of RMW in the modification order.
[2023-11-03; Reflector poll]
NAD. The first reading isn't plausible.
Proposed resolution:
This wording is relative to N4958.
Modify 32.5.4 [atomics.order] as indicated:
-10- Atomic read-modify-write operations shall always read the
-11- Implementations should make atomic stores visible to atomic loads within a reasonable amount of time.lastvalue 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.
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.
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.
[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.
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
Cand an expressionRsuch thatdecltype((R))modelsrange, the following expressions are equivalent:[…]
-2- Given an object
tof typeT, where
(2.1) —
tis a unary function object that accepts a range argument and returns a cv-unqualified class object,[…]
then the implementation ensures that
tis a range adaptor closure object.
is-derived-from-view-interface should require that T is derived from view_interface<T>Section: 25.4.5 [range.view] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-08-22 Last modified: 2023-10-30
Priority: Not Prioritized
View all other issues in [range.view].
View all issues with Tentatively NAD status.
Discussion:
Currently, the wording of is-derived-from-view-interface only detects whether type T is unambiguously
derived from one base class view_interface<U> where U is not required to be T, which is not
the intention of CRTP.
[2023-10-30; Reflector poll]
Set status to Tentatively NAD.
The wording correctly handles the case where T derives from Base
which derives from view_interface<Base>.
We don't want it to only be satisfied for direct inheritance from
view_interface<T>, but from any specialization of
view_interface.
Previously the concept only checked for inheritance from view_base
but it was changed when view_interface stopped inheriting from
view_base.
Proposed resolution:
This wording is relative to N4958.
Modify 25.4.5 [range.view] as indicated:
template<class T> constexpr bool is-derived-from-view-interface = see below; // exposition only template<class T> constexpr bool enable_view = derived_from<T, view_base> || is-derived-from-view-interface<T>;-6- For a type
T,is-derived-from-view-interface<T>istrueif and only ifThas exactly one public base classview_interface<TU>for some typeandUThas no base classes of typeview_interface<Ufor any other typeV>U.V
basic_stringbuf::str()&& should enforce 𝒪(1)Section: 31.8.2.4 [stringbuf.members] Status: Tentatively NAD Submitter: Peter Sommerlad Opened: 2023-10-05 Last modified: 2025-10-24
Priority: 4
View all other issues in [stringbuf.members].
View all issues with Tentatively NAD status.
Discussion:
Recent discussions on llvm-64644
came to the conclusion that basic_stringbuf() && introduced by P0408
might just copy the underlying buffer into a string object and not actually move the allocated space.
While the wording tried to encourage that, especially with the postcondition that the buffer must
be empty afterwards, it failed to specify that the move is never a copy.
𝒪(1) thing.
There might be ABI issues for those who still copy.
Some investigation into 23.2.2.2 [container.reqmts] p.16 and 27.4.3.1 [basic.string.general]
shows that a basic_string as a standard container should move with 𝒪(1).
Unfortunately, we cannot say
str().data() == buf.data()before callingstr()
as a postcondition due to SSO. Maybe a note could be added to eliminate the confusion.
[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]
The requirements are already implied, and proposed wording has no effect.
Proposed resolution:
This wording is relative to N4958.
Modify 31.8.2.4 [stringbuf.members] as indicated:
basic_string<charT, traits, Allocator> str() &&;-9- Postconditions: The underlying character sequence
-10- Returns: Abufis empty andpbase(),pptr(),epptr(),eback(),gptr(), andegptr()are initialized as if by callinginit_buf_ptrs()with an emptybuf.basic_string<charT, traits, Allocator>object move constructed from thebasic_stringbuf's underlying character sequence inbuf. This can be achieved by first adjustingbufto have the same content asview(). [Note: — 23.2.2.2 [container.reqmts] require the move construction of the return value to be𝒪(1)end note]
view_interface::back is overconstrainedSection: 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.
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.
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>);
[…]
};
}
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:
-4- Effects: Equivalent to:!empty()istrue.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()));
chunk_view::outer-iterator::value_type should provide emptySection: 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.
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.
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;
drop_view::begin const may have 𝒪(n) complexitySection: 25.7.12.2 [range.drop.view] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2023-11-08 Last modified: 2025-10-22
Priority: 3
View other active issues in [range.drop.view].
View all other issues in [range.drop.view].
View all issues with Tentatively NAD status.
Discussion:
drop_view::begin const is specified to return ranges::next(ranges::begin(base_),
count_, ranges::end(base_)), which has 𝒪(n) complexity when base_
is a random-access-sized but non-common range (demo):
#include <ranges>
int main() {
const auto s = std::ranges::subrange(std::views::iota(0uz), size_t(-1));
const auto r = std::ranges::drop_view(s, s.size() - 1);
const auto b = r.begin(); // time out
}
[2025-10-22; Reflector poll. Status changed: New → Tentatively NAD]
Set priority to 3 after reflector poll, status to Tentatively NAD.
"NAD, it's Returns: not 'Effects: Equivalent to ...', implementations need to implement it to return that while meeting the amortized 𝒪(1) guarantee."
Proposed resolution:
This wording is relative to N4964.
Modify 25.7.12.2 [range.drop.view] as indicated:
constexpr auto begin()
requires (!(simple-view<V> &&
random_access_range<const V> && sized_range<const V>));
constexpr auto begin() const
requires random_access_range<const V> && sized_range<const V>;
-3- Returns:
(?.?) — If
Vmodelsrandom_access_rangeandsized_range,ranges::begin(base_) + (ranges::distance(base_) - range_difference_t<V>(size()))(?.?) — Otherwise,
ranges::next(ranges::begin(base_), count_, ranges::end(base_)).-4- Remarks: In order to provide the amortized constant-time complexity required by the
[Note 1: Without this, applying arangeconcept whenVdoes not modeldrop_viewsrandom_access_rangeandsized_range, this functionforward_rangethe first overloadcaches the result within thedrop_viewfor use on subsequent calls.reverse_viewover adrop_viewwould have quadratic iteration complexity. — end note]
constexpr auto begin() const requires random_access_range<const V> && sized_range<const V>;
-?- Returns:
ranges::begin(base_) + (ranges::distance(base_) - range_difference_t<const V>(size())).
views::iota(0) | views::take(5) be views::iota(0, 5)?Section: 25.7.10.1 [range.take.overview], 25.7.10.1 [range.take.overview] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2024-01-28 Last modified: 2025-10-20
Priority: Not Prioritized
View other active issues in [range.take.overview].
View all other issues in [range.take.overview].
View all issues with Tentatively NAD status.
Discussion:
Given that C++20 ranges does not introduce the infinite range notification present in range/v3,
this means that views::iota(0) | views::take(5) will currently return a take_view object
that does not model sized_range.
However, with the introduction of C++23 repeat_view, its interaction with views::take/drop
does have special handling depending on whether it is an infinite range, which causes
views::repeat(0) | views::take(5) to return a repeat_view objects that satisfy sized_range.
This inconsistency leads to very different behavior of these two range factories in the case of infinite ranges (demo):
#include <ranges>
auto take_and_drop = std::views::drop(5)
| std::views::take(4)
| std::views::drop(3)
| std::views::take(2)
| std::views::drop(1);
// The type of iota is drop_view<take_view<drop_view<take_view<drop_view<iota_view<int, unreachable_sentinel_t>>>>>>, which is indeed a template bloat.
auto iota = std::views::iota(0) | take_and_drop;
static_assert(std::ranges::sized_range<decltype(iota)>); // failed
// The type of repeat is simply std::ranges::repeat_view<int, long>
std::ranges::sized_range auto repeat = std::views::repeat(0) | take_and_drop; // ok
If we do account for the infinity of repeat_view, then I see no reason not to do it for iota_view,
as this is obviously intuitive and can indeed be considered an enhancement.
[2025-10-20; Reflector poll; Status changed: New → Tentatively NAD.]
"This changes meaning of existing C++20 for unclear benefit. This would need a paper."
"Why does iota(0, 10) | take(5) give you iota(0, 5) but iota(0) | take(5) doesn't?"
"IIRC there was opposition to P1739 introducing any kind of
special cases in the adaptor objects. What got consensus was only the
'specialisations' that preserve the exact type of the underlying range.
Thus iota(0, 10) → iota(0, 5) was fine, but iota(0) → iota(0, 5)
would not have been.
I still think that all changes that simplify the return types are helpful,
but it would certainly be a breaking change now."
Proposed resolution:
This wording is relative to N4971.
Modify 25.7.10.1 [range.take.overview] as indicated:
-2- The name
views::takedenotes a range adaptor object (25.7.2 [range.adaptor.object]). LetEandFbe expressions, letTberemove_cvref_t<decltype((E))>, and letDberange_difference_t<decltype((E))>. Ifdecltype((F))does not modelconvertible_to<D>,views::take(E, F)is ill-formed. Otherwise, the expressionviews::take(E, F)is expression-equivalent to:
(2.1) — if
Tis a specialization ofempty_view(25.6.2.2 [range.empty.view]), then((void)F, decay-copy(E)), except that the evaluations ofEandFare indeterminately sequenced.(2.2) — Otherwise, if
Tmodelsrandom_access_rangeandsized_rangeand is a specialization ofspan(23.7.2.2 [views.span]),basic_string_view(27.3 [string.view]), orranges::subrange(25.5.4 [range.subrange]), thenU(ranges::begin(E), ranges::begin(E) + std::min<D>(ranges::distance(E), F)), except thatEis evaluated only once, whereUis a type determined as follows:
(2.2.1) — if
Tis a specialization ofspan, thenUisspan<typename T::element_type>;(2.2.2) — otherwise, if
Tis a specialization ofbasic_string_view, thenUisT;(2.2.3) — otherwise,
Tis a specialization ofsubrange, andUissubrange<iterator_t<T>>;(2.3) — otherwise, if
Tis a specialization ofiota_view(25.6.4.2 [range.iota.view]) that modelsrandom_access_rangeandsized_range, theniota_view(*ranges::begin(E), *(ranges::begin(E) + std::min<D>(ranges::distance(E), F))), except thatEis evaluated only once.(2.?) — Otherwise, if
Tis a specialization ofiota_viewthat modelsrandom_access_rangeandsame_as<sentinel_t<T>, unreachable_sentinel_t>istrue, thenviews::iota(*ranges::begin(E), *(ranges::begin(E) + static_cast<D>(F))), except thatEis evaluated only once.(2.4) — Otherwise, if
Tis a specialization ofrepeat_view(25.6.5.2 [range.repeat.view]):
(2.4.1) — if
Tmodelssized_range, thenviews::repeat(*E.value_, std::min<D>(ranges::distance(E), F))except thatEis evaluated only once;(2.4.2) — otherwise,
views::repeat(*E.value_, static_cast<D>(F)).(2.5) — Otherwise,
take_view(E, F).
Modify 25.7.12.1 [range.drop.overview] as indicated:
-2- The name
views::dropdenotes a range adaptor object (25.7.2 [range.adaptor.object]). LetEandFbe expressions, letTberemove_cvref_t<decltype((E))>, and letDberange_difference_t<decltype((E))>. Ifdecltype((F))does not modelconvertible_to<D>,views::drop(E, F)is ill-formed. Otherwise, the expressionviews::drop(E, F)is expression-equivalent to:
(2.1) — if
Tis a specialization ofempty_view(25.6.2.2 [range.empty.view]), then((void)F, decay-copy(E)), except that the evaluations ofEandFare indeterminately sequenced.(2.2) — Otherwise, if
Tmodelsrandom_access_rangeandsized_rangeand is
(2.2.1) — a specialization of
span(23.7.2.2 [views.span]),(2.2.2) — a specialization of
basic_string_view(27.3 [string.view]),(2.2.3) — a specialization of
iota_view(25.6.4.2 [range.iota.view]), or(2.2.4) — a specialization of
subrange(25.5.4 [range.subrange]) whereT::StoreSizeisfalse,then
U(ranges::begin(E) + std::min<D>(ranges::distance(E), F), ranges::end(E)), except thatEis evaluated only once, whereUisspan<typename T::element_type>ifTis a specialization ofspanandTotherwise.(2.?) — Otherwise, if
Tis a specialization ofiota_viewthat modelsrandom_access_rangeandsame_as<sentinel_t<T>, unreachable_sentinel_t>istrue, thenviews::iota(*(ranges::begin(E) + static_cast<D>(F))).(2.3) — Otherwise, if
Tis a specialization ofsubrange(25.5.4 [range.subrange]) that modelsrandom_access_rangeandsized_range, thenT(ranges::begin(E) + std::min<D>(ranges::distance(E), F), ranges::end(E), to-unsigned-like(ranges::distance(E) - std::min<D>(ranges::distance(E), F))), except thatEandFare each evaluated only once.(2.4) — Otherwise, if
Tis a specialization ofrepeat_view(25.6.5.2 [range.repeat.view]):
(2.4.1) — if
Tmodelssized_range, thenviews::repeat(*E.value_, ranges::distance(E) - std::min<D>(ranges::distance(E), F))except thatEis evaluated only once;(2.4.2) — otherwise,
((void)F, decay-copy(E)), except that the evaluations ofEandFare indeterminately sequenced.(2.5) — Otherwise,
drop_view(E, F).
ranges::fold_meow should explicitly spell out the return typeSection: 26.4 [algorithm.syn], 26.6.18 [alg.fold] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2024-05-03 Last modified: 2024-06-24
Priority: Not Prioritized
View other active issues in [algorithm.syn].
View all other issues in [algorithm.syn].
View all issues with Tentatively NAD status.
Discussion:
Unlike other algorithms, the return types of ranges::fold_meow are specified in terms of
auto and see below, and its implementation details depend on the return types of
other overloads through decltype(fold_meow(...)).
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.
Modify 26.4 [algorithm.syn], header <algorithm> synopsis, as indicated:
#include <initializer_list> // see 17.11.2 [initializer.list.syn] namespace std { […] namespace ranges { […] template<input_iterator I, sentinel_for<I> S, class T = iter_value_t<I>, indirectly-binary-left-foldable<T, I> F> constexpr auto fold_left(I first, S last, T init, F f) -> decay_t<invoke_result_t<F&, T, iter_reference_t<I>>>; template<input_range R, class T = range_value_t<R>, indirectly-binary-left-foldable<T, iterator_t<R>> F> constexpr auto fold_left(R&& r, T init, F f) -> decay_t<invoke_result_t<F&, T, range_reference_t<R>>>; template<input_iterator I, sentinel_for<I> S, indirectly-binary-left-foldable<iter_value_t<I>, I> F> requires constructible_from<iter_value_t<I>, iter_reference_t<I>> constexpr auto fold_left_first(I first, S last, F f) -> optional<decay_t<invoke_result_t<F&, iter_value_t<I>, iter_reference_t<I>>>>; template<input_range R, indirectly-binary-left-foldable<range_value_t<R>, iterator_t<R>> F> requires constructible_from<range_value_t<R>, range_reference_t<R>> constexpr auto fold_left_first(R&& r, F f) -> optional<decay_t<invoke_result_t<F&, range_value_t<R>, range_reference_t<R>>>>; template<bidirectional_iterator I, sentinel_for<I> S, class T = iter_value_t<I>, indirectly-binary-right-foldable<T, I> F> constexpr auto fold_right(I first, S last, T init, F f) -> decay_t<invoke_result_t<F&, iter_reference_t<I>, T>>; template<bidirectional_range R, class T = range_value_t<R>, indirectly-binary-right-foldable<T, iterator_t<R>> F> constexpr auto fold_right(R&& r, T init, F f) -> decay_t<invoke_result_t<F&, range_reference_t<R>, T>>; template<bidirectional_iterator I, sentinel_for<I> S, indirectly-binary-right-foldable<iter_value_t<I>, I> F> requires constructible_from<iter_value_t<I>, iter_reference_t<I>> constexpr auto fold_right_last(I first, S last, F f) -> optional<decay_t<invoke_result_t<F&, iter_reference_t<I>, iter_value_t<I>>>>; template<bidirectional_range R, indirectly-binary-right-foldable<range_value_t<R>, iterator_t<R>> F> requires constructible_from<range_value_t<R>, range_reference_t<R>> constexpr auto fold_right_last(R&& r, F f) -> optional<decay_t<invoke_result_t<F&, range_reference_t<R>, range_value_t<R>>>>; template<class I, class T> using fold_left_with_iter_result = in_value_result<I, T>; template<class I, class T> using fold_left_first_with_iter_result = in_value_result<I, T>; template<input_iterator I, sentinel_for<I> S, class T = iter_value_t<I>, indirectly-binary-left-foldable<T, I> F> constexprsee 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> constexprsee 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>> constexprsee 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>> constexprsee 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>>>>>; } […] }
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).valuetemplate<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).valuetemplate<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
-5- Effects: Equivalent to:Ubedecltype(ranges::fold_right(first, last, iter_value_t<I>(*first), f)).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> constexprsee 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> constexprsee 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
-7- Effects: Equivalent to:Ubedecay_t<invoke_result_t<F&, T, iter_reference_t<I>>>.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 isfold_left_with_iter_result<I, U>for the first overload andfold_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>> constexprsee 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>> constexprsee 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
Ubedecltype(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 isfold_left_first_with_iter_result<I, optional<U>>for the first overload andfold_left_first_with_iter_result<borrowed_iterator_t<R>, optional<U>>for the second overload.
std::num_get::do_get for bool call the overload for long?Section: 28.3.4.3.2.3 [facet.num.get.virtuals] Status: Tentatively NAD Submitter: Jiang An Opened: 2024-09-29 Last modified: 2025-02-07
Priority: Not Prioritized
View other active issues in [facet.num.get.virtuals].
View all other issues in [facet.num.get.virtuals].
View all issues with Tentatively NAD status.
Discussion:
28.3.4.3.2.3 [facet.num.get.virtuals]/6 currently says:
Effects: If
(str.flags()&ios_base::boolalpha) == 0then input proceeds as it would for alongexcept that if a value is being stored intoval, […]
It is unclear whether an implementation is allowed to call the overload for long in this case.
Currently, libc++'s version calls that overload, while libstdc++ and MSVC STL's don't
(example).
[2025-02-07; Reflector poll: NAD]
I think this is just a libc++ bug.
The wording says it "proceeds as it would for long", which is not the same as
actually making a virtual call to do_get for long. It can either duplicate
the code from do_get for long, or make a non-virtual (i.e. qualified) call
to num_get::do_get.
Proposed resolution:
views::zip and get<T>Section: 24.3.6.3 [indirectcallable.indirectinvocable] Status: Tentatively NAD Submitter: S. B. Tam Opened: 2024-11-01 Last modified: 2025-10-23
Priority: Not Prioritized
View all issues with Tentatively NAD status.
Discussion:
The following use of std::ranges::for_each is valid before P2609R3 and invalid after that.
#include <algorithm>
#include <ranges>
#include <tuple>
using namespace std::ranges;
void f() {
int a[1];
auto fun = [](auto t) {
[[maybe_unused]] auto x = std::get<int&>(t);
};
for_each(views::zip(a), fun);
}
The reason is that, P2609R3 requires fun to be invocable with iter_value_t<I>&,
which is tuple<int>& when I is zip_view's iterator, and tuple<int>&
doesn't support std::get<int&>(t) because there isn't a int& member.
get<int&>(t) in the lambda body.
Note that for_each doesn't actually call fun with iter_value_t<I>, as can be seen by adding
an explicit return type to fun.
Did LWG foresee this impact of P2609R3? Could P2609R3 be reverted to unbreak this code pattern?
[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]
The range concepts are over-constrained by design, and indirect_unary_invocable
always required invocability with iter_value_t. The P2609 changes enforced this
requirement properly for iterators returning proxy references, including zip_iterator.
Proposed resolution:
ranges::cmeow doesn't match ranges::meowSection: 25.3 [range.access] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2024-12-17 Last modified: 2025-02-07
Priority: Not Prioritized
View all issues with Tentatively NAD status.
Discussion:
ranges::begin/rbegin/data can be used on non-ranges as long as the object has a
begin/rbegin/data member, this is also true for their const versions before C++23.
possibly-const-range to the object,
which no longer worked for non-ranges due to this function requiring input_range,
which seems to be a breaking change (demo):
#include <ranges>
struct NotRange {
int* begin();
const int* begin() const;
int* rbegin();
const int* rbegin() const;
int* data();
const int* data() const;
};
int main() {
NotRange r;
(void) std::ranges::begin(r);
(void) std::ranges::rbegin(r);
(void) std::ranges::data(r);
// The following works in C++20, fails in C++23
(void) std::ranges::cbegin(r);
(void) std::ranges::crbegin(r);
(void) std::ranges::cdata(r);
}
[2025-02-07; Reflector poll: NAD]
"We don't need to support ranges::cbegin on non-ranges."
"Seems to be very similar to LWG 3913(i) which LWG closed as NAD."
Proposed resolution:
atomic<void*> should use generic class templateSection: 32.5.8.5 [atomics.types.pointer] Status: Tentatively NAD Submitter: Gonzalo Brito Opened: 2025-01-16 Last modified: 2025-02-07
Priority: Not Prioritized
View other active issues in [atomics.types.pointer].
View all other issues in [atomics.types.pointer].
View all issues with Tentatively NAD status.
Discussion:
32.5.8.5 [atomics.types.pointer] p1 states (emphasis mine):
There is a partial specialization of the
atomicclass template for pointers.
which requires atomic<void*> to use the atomic class template for pointers.
However, the fetch_add/_sub member functions add a difference_type to a T*
which requires a pointer-to-object type (these member functions are constexpr,
so trying to support this seems unimplementable).
atomic_ref, the 32.5.7.5 [atomics.ref.pointer] p1 states (emphasis mine):
There are specializations of the
atomic_ref` class template for all pointer-to-object types.
which avoids this issue and applying the same form to 32.5.8.5 [atomics.types.pointer] would make
atomic<void*> and atomic_ref<void*> consistent.
atomic<void*> uses the general template. Therefore, no user
code seems to be impacted.
[2025-02-07; Reflector poll: NAD]
The fetch_OP members have "Mandates: T is a complete object type."
and a note explaining that this means arithmetic on void* is ill-formed.
So implementations are expected to use the partial specialization for void*
but to reject attempts at arithmetic. They all do this correctly today.
Proposed resolution:
This wording is relative to N5001.
Modify 32.5.8.5 [atomics.types.pointer] as indicated:
-1- There is a partial specialization of the
atomicclass template forpointerspointer-to-object types. Specializations of this partial specialization are standard-layout structs. They each have a trivial destructor.
std::vector::erase[_if] should be based on ranges removeSection: 23.3.13.6 [vector.erasure] Status: Tentatively NAD Submitter: Peter Kasting Opened: 2025-03-05 Last modified: 2025-10-21
Priority: Not Prioritized
View all issues with Tentatively NAD status.
Discussion:
C++20 added std::vector::erase[_if]. Per 23.3.13.6 [vector.erasure], these are equivalent
to a call to std::remove[_if] followed by an appropriate erase.
std::remove_if is specified (by 26.7.8 [alg.remove]) as invoking
its predicate as pred(*i), while std::ranges::remove_if uses the more flexible
invoke(pred, invoke(proj, *i)). Disregarding the projection, the latter allows the use of member function
pointers as predicates, while the former does not.
I assume the committee intentionally did not change the non-ranges version to use invoke because it
caused a backwards-compatibility risk. (If I am mistaken and this was an oversight, perhaps this and
other non-ranges algorithms that take predicates should be updated to use invoke() to invoke them.)
If that's true, though, it's perplexing why a new-to-c++20 function like std::vector::erase_if
should suffer the same drawback.
[2025-10-21; Reflector poll. Status changed: New → Tentatively NAD.]
"This is a design change and needs a paper."
[2025-10-21; .]
"Paper should discuss whether std::erase_if(c, &T::mf) works on paper,
currently works on STL implementations in the field, and whether it should work."
Proposed resolution:
This wording is relative to N5001.
Modify 23.3.13.6 [vector.erasure] as indicated:
template<class T, class Allocator, class U = T> constexpr typename vector<T, Allocator>::size_type erase(vector<T, Allocator>& c, const U& value);-1- Effects: Equivalent to:
auto rit= ranges::remove(c.begin(), c.end(), value);auto r = distance(it, c.end());c.erase(r.begin()it, rc.end()); return r.size();template<class T, class Allocator, class Predicate> constexpr typename vector<T, Allocator>::size_type erase_if(vector<T, Allocator>& c, Predicate pred);-2- Effects: Equivalent to:
auto rit= ranges::remove_if(c.begin(), c.end(), pred);auto r = distance(it, c.end());c.erase(r.begin()it, rc.end()); return r.size();
vector<bool, Allocator> mandate that Allocator::value_type is bool?Section: 23.3.14.1 [vector.bool.pspc] Status: Tentatively NAD Submitter: Stephan T. Lavavej Opened: 2025-03-18 Last modified: 2025-06-13
Priority: Not Prioritized
View all other issues in [vector.bool.pspc].
View all issues with Tentatively NAD status.
Discussion:
N5008 23.3.14.1 [vector.bool.pspc]/2 says:
Unless described below, all operations have the same requirements and semantics as the primary
vectortemplate, except that operations dealing with theboolvalue type map to bit values in the container storage andallocator_traits::construct(20.2.9.3 [allocator.traits.members]) is not used to construct these values.
23.2.2.5 [container.alloc.reqmts]/5 says:
Mandates:
allocator_type::value_typeis the same asX::value_type.
Is vector<bool, allocator<int>> forbidden? There's implementation divergence:
MSVC's STL enforces the mandate, while libc++ and libstdc++ accept this code, discovered while
running libc++'s tests with MSVC's STL.
vector<bool>
should be unique among containers in accepting allocator<Anything>, then I believe that
a normative sentence should be added to 23.3.14.1 [vector.bool.pspc]/2, specifically creating an exemption
to 23.2.2.5 [container.alloc.reqmts]/5.
[2025-06-13; Reflector poll]
Set status to Tentatively NAD. This is just a bug in some implementations (now fixed in libstdc++).
Proposed resolution:
std::ranges::to with union return typeSection: 25.5.7.2 [range.utility.conv.to], 25.5.7.3 [range.utility.conv.adaptors] Status: Tentatively NAD Submitter: Jiang An Opened: 2025-03-20 Last modified: 2025-10-20
Priority: Not Prioritized
View other active issues in [range.utility.conv.to].
View all other issues in [range.utility.conv.to].
View all issues with Tentatively NAD status.
Discussion:
LWG 3847(i) made std::ranges::to require the return type (or the target type for the overload
returning range adaptor closure object) to be a cv-unqualified class type. Although the term
"class type" in core language specification also covers union types, implementations (libstdc++ and MSVC STL)
tend to implement this part of the Mandates only with std::is_class_v, which rejects union types.
#include <memory>
#include <ranges>
#include <type_traits>
#include <utility>
#include <vector>
template<class T, class A = std::allocator<T>>
union weird_vector {
std::vector<T, A> vec_;
constexpr weird_vector() : vec_() {}
constexpr weird_vector(const weird_vector& other) : vec_(other.vec_) {}
constexpr weird_vector(weird_vector&& other) noexcept : vec_(std::move(other.vec_)) {}
template<class U>
requires (!std::same_as<std::remove_cvref_t<U>, weird_vector>) &&
(!std::same_as<std::remove_cvref_t<U>, std::vector<T, A>>) &&
requires(U&& u) { std::vector<T, A>(std::forward<U>(u)); }
constexpr explicit weird_vector(U&& u) : vec_(std::forward<U>(u)) {}
template<class T1, class T2, class... Ts>
requires requires(T1&& t1, T2&& t2, Ts&&... ts) {
std::vector<T, A>(std::forward<T1>(t1), std::forward<T2>(t2), std::forward<Ts>(ts)...);
}
constexpr weird_vector(T1&& t1, T2&& t2, Ts&&... ts)
: vec_(std::forward<T1>(t1), std::forward<T2>(t2), std::forward<Ts>(ts)...) {}
constexpr weird_vector& operator=(const weird_vector& other) {
vec_ = other.vec_;
return *this;
}
constexpr weird_vector& operator=(weird_vector&& other)
noexcept(std::is_nothrow_move_assignable_v<std::vector<T, A>>) {
vec_ = std::move(other.vec_);
return *this;
}
constexpr ~weird_vector() {
vec_.~vector();
}
};
int main() {
int arr[]{42, 1729};
auto v [[maybe_unused]] = std::ranges::to<weird_vector<int>>(arr);
}
Although libc++ currently accepts this example, the acceptance seems to be a bug, because libc++ hasn't implemented the "class" part in the Mandates at all (llvm/llvm-project#132133).
It's unclear whether union types were intended to be accepted. Perhaps we should follow implementations' choices and reject them.[2025-10-20; Reflector poll; Status changed: New → Tentatively NAD.]
"Those implementations have bugs and should be fixed."
There's no intrinsic reason why unions (with suitable constructors) should be rejected here."
Proposed resolution:
This wording is relative to N5008.
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:
[…]Cis a cv-unqualified non-union class type.
Modify 25.5.7.3 [range.utility.conv.adaptors] as indicated:
template<class C, class... Args> requires (!view<C>) constexpr auto to(Args&&... args); template<template<class...> class C, class... Args> constexpr auto to(Args&&... args);-1- Mandates: For the first overload,
[…]Cis a cv-unqualified non-union class type.
compare_exchange_strong is unclearSection: 32.5.7.2 [atomics.ref.ops], 32.5.8.2 [atomics.types.operations] Status: Tentatively NAD Submitter: jim x Opened: 2025-04-17 Last modified: 2025-10-20
Priority: Not Prioritized
View other active issues in [atomics.ref.ops].
View all other issues in [atomics.ref.ops].
View all issues with Tentatively NAD status.
Discussion:
Both compare_exchange_strong and compare_exchange_weak share the same specified rule
If and only if the comparison is
falsethen, after the atomic operation, the value inexpectedis replaced by the value read from the value referenced by*ptrduring the atomic comparison.
However, there is a remark for the weak version
A weak compare-and-exchange operation may fail spuriously.
That is, even when the contents of memory referred to by expected and ptr are equal,
it may return false and store back to expected the same memory contents that were
originally there.
compare_exchange_strong can have the spurious failed comparison.
[2025-10-20; Reflector poll. Status changed: New → Tentatively NAD.]
"The Effects: explain the behaviour, and don't allow for spurious failures."
"The Remarks: are normative and are explicitly adding permission to fail spuriously. Absent such permission, the specification for strong operations is clear and has no spurious failures."
Proposed resolution:
range_formatter::formatSection: 28.5.7.2 [format.range.formatter] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2025-04-18 Last modified: 2025-06-12
Priority: Not Prioritized
View all other issues in [format.range.formatter].
View all issues with Tentatively NAD status.
Discussion:
Currently, the signature of range_formatter::format is as follows:
template<ranges::input_range R, class FormatContext>
requires formattable<ranges::range_reference_t<R>, charT> &&
same_as<remove_cvref_t<ranges::range_reference_t<R>>, T>
typename FormatContext::iterator
format(R&& r, FormatContext& ctx) const;
which requires that the reference type of the range parameter must be formattable,
and such type must be exactly T after removing the cvref-qualifiers.
However, satisfying the latter always implies satisfying the former, as the range_formatter class
already requires that T must be formattable.
There is no need to perform a redundant check here.
[2025-06-12; Reflector poll]
Set status to Tentatively NAD. This is not redundant, it might check that
const T is formattable, which is not the same as checking that
T is formattable.
Proposed resolution:
This wording is relative to N5008.
Modify 28.5.7.2 [format.range.formatter] as indicated:
[…]namespace std { template<class T, class charT = char> requires same_as<remove_cvref_t<T>, T> && formattable<T, charT> class range_formatter { […] template<ranges::input_range R, class FormatContext> requiresformattable<ranges::range_reference_t<R>, charT> &&same_as<remove_cvref_t<ranges::range_reference_t<R>>, T> typename FormatContext::iterator format(R&& r, FormatContext& ctx) const; }; }template<ranges::input_range R, class FormatContext> requiresformattable<ranges::range_reference_t<R>, charT> &&same_as<remove_cvref_t<ranges::range_reference_t<R>>, T> typename FormatContext::iterator format(R&& r, FormatContext& ctx) const;-11- Effects: Writes the following into
ctx.out(), adjusted according to the range-format-spec:
Section: 24.3.1 [iterator.requirements.general], 25.4.3 [range.approximately.sized], 25.4.1 [range.req.general], 25.4.2 [range.range], 25.4.4 [range.sized], 25.7.8.2 [range.filter.view], 25.7.12.2 [range.drop.view], 25.7.13.2 [range.drop.while.view], 25.7.17.2 [range.split.view], 25.7.21.2 [range.reverse.view], 25.7.30.2 [range.slide.view], 25.7.31.2 [range.chunk.by.view] Status: Tentatively NAD Submitter: Andreas Weis Opened: 2025-06-02 Last modified: 2025-10-23
Priority: Not Prioritized
View all other issues in [iterator.requirements.general].
View all issues with Tentatively NAD status.
Discussion:
Currently range views that cache the result of their operation claim an amortized 𝒪(1) worst-case runtime complexity. This is inconsistent with the established practice in algorithm analysis, where the given complexity bound must hold for all possible sequences of operations. Caching is not sufficient to lower the complexity bound here, as the sequence that contains only a single call to the operation will cause a runtime cost linear in the size of the underlying range. Thus all of the caching range operations are in fact 𝒪(n).
Apart from the caching view operations, this also has secondary impacts in other places that rely on the complexity of iterator functions, such as the iterator requirements and functions for computing the size of a range. It is unclear how desirable it is under these circumstances to continue disallowing other kinds of 𝒪(n) behavior for iterator functions. While caching offers clear benefits in the context of lazy evaluation, it cannot prevent losing the 𝒪(1) complexity guarantee. The proposed changes below therefore do not address the issue that other types of views (such as hypothetical non-caching variants of the affected views) that were previously considered invalid will become valid with these changes.[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]
Relaxing complexity of ranges::begin/ranges::end is design change,
and not direction we want to pursue.
The "amortized constant" are not quite right words to describe intended requirements. A rework preserving the intent of current wording would be welcomed.
Proposed resolution:
This wording is relative to N5008.
Modify 24.3.1 [iterator.requirements.general] as indicated:
-14- All the categories of iterators require only those functions that are realizable for a given category in
constant time (amortized)linear time. Therefore, requirement tables and concept definitions for the iterators do not specify complexity.
Modify 25.4.3 [range.approximately.sized] as indicated:
-1- The
approximately_sized_rangeconcept refines range with the requirement that an approximation of the number of elements in the range can be determined inamortized constantlinear time usingranges::reserve_hint.
Modify 25.4.1 [range.req.general] as indicated:
-2- The
rangeconcept requires thatranges::beginandranges::endreturn an iterator and a sentinel, respectively. Thesized_rangeconcept refines range with the requirement thatranges::sizebeamortized𝒪(1n). Theviewconcept specifies requirements on arangetype to provide operations with predictable complexity.
Modify 25.4.2 [range.range] as indicated:
-2- Given an expression
tsuch thatdecltype((t))isT&,Tmodelsrangeonly if
(2.1) — […]
(2.2) — both
ranges::begin(t)andranges::end(t)areamortized constantlinear time and non-modifying, and(2.3) — […]
Modify 25.4.4 [range.sized] as indicated:
-1- The
sized_rangeconcept refinesapproximately_sized_rangewith the requirement that the number of elements in the range can be determined inamortized constantlinear time usingranges::size.template<class T> concept sized_range = approximately_sized_range<T> && requires(T& t) { ranges::size(t); };-2- Given an lvalue
tof typeremove_reference_t<T>,Tmodelssized_rangeonly if
(2.1) —
ranges::size(t)isamortized𝒪(1n), does not modifyt, and is equal toranges::distance(ranges::begin(t), ranges::end(t)), and(2.2) — […]
Modify 25.7.8.2 [range.filter.view] as indicated:
constexpr iterator begin();-3- Preconditions: […]
-4- Returns: […] -5- Remarks:In order to provide the amortized constant time complexity required by theThis function caches the result within therangeconcept whenfilter_viewmodelsforward_range, thisfilter_viewfor use on subsequent calls.
Modify 25.7.12.2 [range.drop.view] as indicated:
constexpr auto begin() requires (!(simple-view<V> && random_access_range<const V> && sized_range<const V>)); constexpr auto begin() const requires random_access_range<const V> && sized_range<const V>;-3- Returns: […]
-4- Remarks:In order to provide the amortized constant-time complexity required by theThe first overload caches the result within therangeconcept whendrop_viewmodelsforward_range, thedrop_viewfor use on subsequent calls.
Modify 25.7.13.2 [range.drop.while.view] as indicated:
constexpr auto begin();-3- Preconditions: […]
-4- Returns: […] -5- Remarks:In order to provide the amortized constant-time complexity required by theThe first call caches the result within therangeconcept whendrop_while_viewmodelsforward_range, thedrop_while_viewfor use on subsequent calls.
Modify 25.7.17.2 [range.split.view] as indicated:
constexpr iterator begin();-3- Returns: […]
-4- Remarks:In order to provide the amortized constant time complexity required by theThis function caches the result within therangeconcept, thissplit_viewfor use on subsequent calls.
Modify 25.7.21.2 [range.reverse.view] as indicated:
constexpr reverse_iterator<iterator_t<V>> begin();-2- Returns: […]
-3- Remarks:In order to provide the amortized constant time complexity required by theThis function caches the result within therangeconcept, thisreverse_viewfor use on subsequent calls.
Modify 25.7.30.2 [range.slide.view] as indicated:
constexpr auto begin() requires (!(simple-view<V> && slide-caches-nothing<const V>));[…]-3- Returns: […]
-4- Remarks:In order to provide the amortized constant-time complexity required by theThis function caches the result within therangeconcept, thisslide_viewfor use on subsequent calls whenVmodelsslide-caches-first.constexpr auto end() requires (!(simple-view<V> && slide-caches-nothing<const V>));-6- Returns: […]
-7- Remarks:In order to provide the amortized constant-time complexity required by theThis function caches the result within therangeconcept, thisslide_viewfor use on subsequent calls whenVmodelsslide-caches-first.
Modify 25.7.31.2 [range.chunk.by.view] as indicated:
constexpr iterator begin();-3- Preconditions: […]
-4- Returns: […] -5- Remarks:In order to provide the amortized constant-time complexity required by theThis function caches the result within therangeconcept, thischunk_by_viewfor use on subsequent calls.
seq_cst operations ordered in the single total order if these two operations are unsequenced?Section: 32.5.4 [atomics.order] Status: Tentatively NAD Submitter: jim x Opened: 2025-08-06 Last modified: 2025-10-23
Priority: Not Prioritized
View other active issues in [atomics.order].
View all other issues in [atomics.order].
View all issues with Tentatively NAD status.
Discussion:
Consider this snippet code:
std::atomic<int> flag = {0};
std::atomic<int> turn = {0};
if(flag + turn){}
The loads of flag and turn as the operands of + are unsequenced according to [intro.execution] p10.
Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.
However, 32.5.4 [atomics.order] p4 says:
There is a single total order S on all
memory_order::seq_cstoperations, including fences, that satisfies the following constraints.
Then, it says that:
First, if A and B are
memory_order::seq_cstoperations and A strongly happens before B, then A precedes B in S.
According to the first sentence, the load of flag and the load of turn do have an order in the single
total order S since they are both memory_order::seq_cst operations. However, since they are unsequenced,
the second sentence does not apply to them. So, what's the order of them in S? Is the order of them in
S unspecified?
[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]
The loads are both done within functions calls, so are at worst indeterminately sequenced. So we already say what the suggested resolution wants.
Proposed resolution:
Section: 32.5.8.2 [atomics.types.operations] Status: Tentatively NAD Submitter: jim x Opened: 2025-08-20 Last modified: 2025-10-23
Priority: Not Prioritized
View other active issues in [atomics.types.operations].
View all other issues in [atomics.types.operations].
View all issues with Tentatively NAD status.
Discussion:
Consider this example:
std::atomic<int> v = 0; // thread 1: v.store(1, memory_order::release); // #1 // thread 2: v.load(memory_order::acquire); // #2
Say, #2 reads the value written by #1, #1 synchronizes with #2. According to 6.10.2.2 [intro.races] p7:
An evaluation A happens before an evaluation B (or, equivalently, B happens after A) if either
(7.1) — […]
(7.2) — A synchronizes with B, or
(7.3) — […]
So, #1 happens before B. However, 6.10.1 [intro.execution] p12 says:
For each
(12.1) — function invocation,
(12.2) — […]
(12.3) — […]
F, each evaluation that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any) is either sequenced before all evaluations that occur within F or sequenced after all evaluations that occur within F;
Because both v.store(...) and v.load(...) are function invocations, and we can think that the member
functions comprise some evaluations to form the operation, therefore, how are these evaluations that
occur within the store ordered with those within the load?
store synchronizes with the load, hence, the evaluation of the function
call expression v.store(...) happens before the evaluation of the function call expression v.load(...),
but how about these evaluations occurring within these functions?
A possible resolution might be: The order between all evaluations occurring within a function invocation
and another evaluation B is determined by how the evaluation of the function call expression is
ordered in relation to the expression B.
For example, if v.store() happens-before E, then all evaluations occurring within the store
happen-before E. As well, v.store(...) synchronizes with v.load(...), then all evaluations
occurring within v.store(...) synchronize with all evaluations occurring within v.load(...).
[2025-10-23; Reflector poll; Status changed: New → Tentatively NAD.]
The load/store functions invocations participate in happens-before as atomic (indivisible) units.
Proposed resolution:
co_await change_coroutine_scheduler(s) requires assignableSection: 33.13.6.5 [task.promise] Status: Tentatively NAD Submitter: Dietmar Kühl Opened: 2025-08-31 Last modified: 2025-10-27
Priority: Not Prioritized
View other active issues in [task.promise].
View all other issues in [task.promise].
View all issues with Tentatively NAD status.
Discussion:
Addresses US 256-383
The specification of change_coroutine_scheduler(sched) uses
std::exchange to put the scheduler into place
(in 33.13.6.5 [task.promise] paragraph 11).
The problem is that std::exchange(x, v) expects
x to be assignable from v but there is
no requirement for scheduler to be assignable.
[2025-10-23; Reflector poll. Status → Tentatively NAD .]
"scheduler requires copyable which requires assignment."
Proposed resolution:
Change the wording in 33.13.6.5 [task.promise] paragraph 11
to avoid the use of std::exchange and transfer the using
construction:
template<class Sch> auto await_transform(change_coroutine_scheduler<Sch> sch) noexcept;-11- Effects: Equivalent to:
return await_transform(just(exchange(SCHED(*this), scheduler_type(sch.scheduler))), *this);auto* s{address_of(SCHED(*this))}; auto rc{std::move(*s)}; s->~scheduler_type(); new(s) scheduler_type(std::move(sch)); return std::move(rc);
constexpr for inplace_stop_token and inplace_stop_sourceSection: 32.3.8 [stoptoken.inplace] Status: Tentatively NAD Submitter: Lewis Baker Opened: 2025-08-28 Last modified: 2025-10-17
Priority: Not Prioritized
View all issues with Tentatively NAD status.
Discussion:
The inplace_stop_source::get_token() member function is declared constexpr,
but there are no constexpr member-functions declared on inplace_stop_token,
making the utility of being able to call this member function during constant
evaluation limited.
inplace_stop_token also be declared constexpr?
i.e. operator==, swap(), stop_possible() and stop_requested().
The operator== and stop_possible() and swap() member functions should be
able to be made constexpr trivially as they are just required to compare/modify
pointers to the associated stop source.
The stop_requested() member function is specified to be equivalent to calling
stop_requested() on the associated inplace_stop_source (if any), which is not
currently declared constexpr primarily because its implementation requires
synchronisation/atomic operations.
Now that std::atomic operations are now constexpr, it may be possible/appropriate
for stop_requested() on both inplace_stop_source and inplace_stop_token to also
be declared constexpr.
[2025-10-17; Reflector poll. Status changed: New → Tentatively NAD.]
This allows constant-initializing a token, it's basically a constructor.
Other member functions don't need to be constexpr,
similar to how std::mutex::lock() doesn't need to be constexpr
for constant-init of std::mutex to be useful.
Proposed resolution:
This wording is relative to N5014.
[Drafting note:: This is the minimum proposed wording change. Additionally, consider adding
constexprto the declaration ofinplace_stop_token::stop_requested()(in 32.3.8.1 [stoptoken.inplace.general] and 32.3.8.2 [stoptoken.inplace.mem]) and toinplace_stop_source::stop_requested()(in 32.3.9.1 [stopsource.inplace.general] and 32.3.9.3 [stopsource.inplace.mem])]
Modify 32.3.8.1 [stoptoken.inplace.general], class inplace_stop_token synopsis, as indicated:
namespace std {
class inplace_stop_token {
public:
template<class CallbackFn>
using callback_type = inplace_stop_callback<CallbackFn>;
constexpr inplace_stop_token() = default;
constexpr bool operator==(const inplace_stop_token&) const = default;
// 32.3.8.2 [stoptoken.inplace.mem], member functions
bool stop_requested() const noexcept;
constexpr bool stop_possible() const noexcept;
constexpr void swap(inplace_stop_token&) noexcept;
private:
const inplace_stop_source* stop-source = nullptr; // exposition only
};
}
Modify 32.3.8.2 [stoptoken.inplace.mem] as indicated:
[Drafting note:: As a drive-by fix this adds the missing return type
boolto thestop_possible()prototype]
constexpr void swap(inplace_stop_token& rhs) noexcept;[…]-1- Effects: Exchanges the values of
stop-sourceandrhs.stop-source.constexpr bool stop_possible() const noexcept;-4- Returns:
stop-source != nullptr.
transform_sender comparing types ignoring cv-qualifiers doesn't take into account differences in value categorySection: 33.9.6 [exec.snd.transform] Status: Tentatively NAD Submitter: Lewis Baker Opened: 2025-08-28 Last modified: 2025-10-23
Priority: Not Prioritized
View all issues with Tentatively NAD status.
Discussion:
In 33.9.6 [exec.snd.transform] p1, the specification for transform_sender() states:
Let
transformed-sndrbe the expressiondom.transform_sender(std::forward<Sndr>(sndr), env...)if that expression is well-formed; otherwise,
default_domain().transform_sender(std::forward<Sndr>(sndr), env...)Let
final-sndrbe the expressiontransformed-sndriftransformed-sndrandsndrhave the same type ignoring cv-qualifiers; otherwise, it is the expressiontransform_sender(dom, transformed-sndr, env...).
However, the use of the phrase "have the same type ignoring cv-qualifiers" asks to compare
the types without const or volatile qualifiers, but doesn't take into account differences
in value category of the types of these expressions.
sndr might have type T&& and transformed-sndr
might return a new prvalue of type T.
My interpretation of the current wording is that I should apply the test
same_as<remove_cv_t<decltype(sndr)>, remove_cv_t<decltype(transformed-sndr)>>.
However, remove_cv_t does not remove reference-qualifiers from a type Sndr&&
(which in the above example, is the type of sndr), and thus would compare as different to
a transform-sndr type of Sndr.
I believe the intention is that this should instead use
same_as<remove_cvref_t<decltype(sndr)>, remove_cvref_t<decltype(transformed-sndr)>>.
For example, the
implementation in NVIDIA's stdexec repository uses same_as<decay_t<T>, decay_t<U>>
for this check.
The wording should be modified to use a phrase that removes both reference and cv-qualifiers when comparing types.
[2025-10-23; Reflector poll. Status → Tentatively NAD .]
"NAD, expressions never have reference type."
Proposed resolution:
This wording is relative to N5014.
Modify 33.9.6 [exec.snd.transform] as indicated:
namespace std::execution { template<class Domain, sender Sndr, queryable... Env> requires (sizeof...(Env) <= 1) constexpr sender decltype(auto) transform_sender(Domain dom, Sndr&& sndr, const Env&... env) noexcept(see below); }-1- Let
transformed-sndrbe the expressiondom.transform_sender(std::forward<Sndr>(sndr), env...)if that expression is well-formed; otherwise,
default_domain().transform_sender(std::forward<Sndr>(sndr), env...)Let
final-sndrbe the expressiontransformed-sndriftransformed-sndrandsndrhave the same decayed typeignoring cv-qualifiers; otherwise, it is the expressiontransform_sender(dom, transformed-sndr, env...).
empty/size should be noexceptSection: 23.6.3.1 [queue.defn], 23.6.4.1 [priqueue.overview], 23.6.6.2 [stack.defn] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2025-09-09 Last modified: 2025-10-22
Priority: Not Prioritized
View all other issues in [queue.defn].
View all issues with Tentatively NAD status.
Discussion:
C++23 container adaptors flat_meow all have noexcept size/empty members.
size/empty members of other container adaptors are not mark noexcept,
even though they behave the same as flat_meow that returning the size/empty
of the underlying container.
It makes sense to make them noexcept as well for consistency. Although the standard doesn't
explicitly say those two members of the container must not throw, the fact that all standard
containers and common third-party containers mark them as unconditionally noexcept implies
that it's perfectly reasonable to assume that they never will.
[2025-10-22 Reflector poll. Status changed: New → Tentatively NAD.]
General disagrement with the proposed change.
Implicitly changing container requirements.
We should fix flat_ adaptors instead.
Proposed resolution:
This wording is relative to N5014.
Modify 23.6.3.1 [queue.defn] as indicated:
namespace std {
template<class T, class Container = deque<T>>
class queue {
public:
[…]
constexpr bool empty() const noexcept { return c.empty(); }
constexpr size_type size() const noexcept { return c.size(); }
[…]
};
[…]
}
Modify 23.6.4.1 [priqueue.overview] as indicated:
namespace std {
template<class T, class Container = vector<T>,
class Compare = less<typename Container::value_type>>
class priority_queue {
public:
[…]
constexpr bool empty() const noexcept { return c.empty(); }
constexpr size_type size() const noexcept { return c.size(); }
[…]
};
[…]
}
Modify 23.6.6.2 [stack.defn] as indicated:
namespace std {
template<class T, class Container = deque<T>>
class stack {
public:
[…]
constexpr bool empty() const noexcept { return c.empty(); }
constexpr size_type size() const noexcept { return c.size(); }
[…]
};
[…]
}
simd::basic_vec constructorSection: 29.10.7.2 [simd.ctor] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2025-09-29 Last modified: 2025-10-17
Priority: Not Prioritized
View other active issues in [simd.ctor].
View all other issues in [simd.ctor].
View all issues with Tentatively NAD status.
Discussion:
The broadcasting, generator-based, and range constructors of simd::basic_vec all take a single
argument, and their constraints are not mutually exclusive.
value_type, this will lead to ambiguity:
#include <simd>
struct S {
operator double() const; // basic_vec(U&& value)
double operator()(int) const; // basic_vec(G&& gen)
double* begin() const; // basic_vec(R&& r, flags<Flags...> = {});
double* end() const;
constexpr static int size() { return 2; }
};
int main() {
std::simd::vec<double> simd(S{}); // error: call of overloaded 'basic_simd(S)' is ambiguous
}
Do we need more constraints, similar to the one in string_view(R&& r) that requires
R not to be convertible to const char*, to make the above work, i.e., only invoke the
broadcasting constructor?
[2025-10-17; Reflector poll. Status changed: New → Tentatively NAD.]
Users of such types should do disambiguation explicitly, basic_vec should not guess what they mean.
Proposed resolution:
This wording is relative to N5014.
Modify 29.10.7.2 [simd.ctor] as indicated:
template<class G> constexpr explicit basic_vec(G&& gen);[…]-8- Let
-9- Constraints:Fromidenote the typedecltype(gen(integral_constant<simd-size-type, i>())).
(9.?) —
constructible_from<value_type, G>isfalse.(9.?) —
Fromisatisfiesconvertible_to<value_type>for alliin the range of [0, size()). In addition, for all i in the range of [0, size()), ifFromiis an arithmetic type, conversion fromFromitovalue_typeis value-preserving.template<class R, class... Flags> constexpr basic_vec(R&& r, flags<Flags...> = {}); template<class R, class... Flags> constexpr basic_vec(R&& r, const mask_type& mask, flags<Flags...> = {});-12- Let mask be
-13- Constraints:mask_type(true)for the overload with nomaskparameter.
(13.1) —
Rmodelsranges::contiguous_rangeandranges::sized_range,(13.2) —
ranges::size(r)is a constant expression,and(13.3) —
ranges::size(r)is equal tosize().,(13.?) —
constructible_from<value_type, R>isfalse, and(13.?) —
r(integral_constant<simd-size-type, 0>())is not a valid expression.
span(R&&) CTAD apply P2280?Section: 23.7.2.2.3 [span.deduct] Status: Tentatively NAD Submitter: Hewill Kang Opened: 2025-10-04 Last modified: 2025-10-20
Priority: Not Prioritized
View all issues with Tentatively NAD status.
Discussion:
Thanks to P2280R4, simd::basic_vec's CTAD can specify template parameters
directly through ranges::size:
basic_vec(R&& r, ...) -> vec<ranges::range_value_t<R>, ranges::size(r)>
However, span with similar CTAD forms do not have this automatic static size optimization applied:
span(R&&) -> span<remove_reference_t<ranges::range_reference_t<R>>>;
Do we need to do it for span?
span constructor actually requires R to be a contiguous_range and a
sized_range. If it is further required that ranges::size be a constant expression, only raw array,
array, span, ranges::empty_view, and ranges::single_view satisfy this requirement.
Given that span already has CTAD for raw arrays and arrays, this improvement is not
significant, but it still seems worthwhile as a compile-time optimization for certain
user-defined types or in some specialized cases (demo):
#include <array>
#include <ranges>
constexpr std::size_t N = 42;
auto to_span(auto& r) {
static_assert(std::ranges::size(r) == N); // ok after P2280
return std::span(r);
}
std::array<int, N> a;
auto s1 = to_span(a);
static_assert(std::same_as<decltype(s1), std::span<int, N>>);
auto r = std::array<int, N>{} | std::views::as_const; // as_const_view<owning_view<array<int, N>>>
auto s2 = to_span(r);
static_assert(std::same_as<decltype(s2), std::span<const int, N>>); // fire, ok after this PR
[2025-10-20 Reflector poll. Status changed: NAD → Tentatively NAD.]
This is breaking change, and we need a paper with analysis.
Proposed resolution:
This wording is relative to N5014.
Modify 23.7.2.2.1 [span.overview] as indicated:
namespace std {
template<class ElementType, size_t Extent = dynamic_extent>
class span {
[…]
};
[…]
template<class R>
span(R&& r) -> see belowspan<remove_reference_t<ranges::range_reference_t<R>>>;
}
Modify 23.7.2.2.3 [span.deduct] as indicated:
template<class R> span(R&& r) -> see belowspan<remove_reference_t<ranges::range_reference_t<R>>>;-2- Constraints:
-?- Remarks: LetRsatisfiesranges::contiguous_range.Tdenote the typeremove_reference_t<ranges::range_reference_t<R>>. The deduced type is equivalent to
(?.1) —
span<T, static_cast<size_t>(ranges::size(r))>ifRsatisfiesranges::sized_rangeandranges::size(r)is a constant expression.(?.2) —
span<T>otherwise.