Revised 2024-12-22 at 11:35:55 UTC
Section: 23.2.7.1 [associative.reqmts.general] Status: Tentatively Ready Submitter: Joaquín M López Muñoz Opened: 2021-08-04 Last modified: 2024-12-09
Priority: 3
View other active issues in [associative.reqmts.general].
View all other issues in [associative.reqmts.general].
View all issues with Tentatively Ready status.
Discussion:
For the expression a.merge(a2)
, postconditions say that "iterators referring to the transferred elements
[…] now behave as iterators into a
[…]". When a
and a2
are of different
types, this seems to imply, under the widest interpretation for "behaving as", that a
-iterators and
a2
-iterators are actually of the same type, that is, that associative containers have SCARY iterators,
which is, to the best of my knowledge, not currently mandated by the standard.
There are (at least) three possible resolutions to this ambiguity, ordered by intrusiveness:
Indicate that "behaving as" only applies to the case where a
and a2
are of the same type.
Clarify what "behaving as" means. A non-SCARY interpretation is that an a2
-iterator to a transferred
element can still be dereferenced, incremented (if not past the last element of a
) and decremented (if
not pointing to the first element of a
), while comparison with a
-iterators and use in the
interface of a
is not guaranteed.
Mandate SCARY iterators by, for instance, requiring that associative containers with compatible nodes (23.2.5.1 [container.node.overview]/1) have the same iterator types.
Note that this issue does not extend to unordered associative containers, as there (23.2.8.1 [unord.req.general]) iterators to transferred elements are invalidated, which makes the point of SCARYness irrelevant. That said, if SCARY iterators are finally required for associative containers, it makes much sense to extend the requirement to unordered associative containers as well.
[2021-08-20; Reflector poll]
Set priority to 3 after reflector poll.
[2024-12-04; Jonathan provides wording]
If we want to require SCARY iterators then that should be a proposal that goes through LEWG design review. I propose an almost minimal change to make the spec consistent without imposing any new requirements on implementations.
The minimal change would be to say that iterators remain valid if a
and a2
have the same type, which is the minimum portable guarantee that always holds.
However what matters in practice is whether the iterator types are the same.
That would not be a portable guarantee, because it depends on whether the
implementation uses SCARY iterators for its maps and sets, so users could
write code that works on one implementation and fails to compile when moved
to a different implementation. But that portability trap would be present
even if we only say iterators remain valid when a
and a2
are the same type.
If the code compiles and works on an implementation with SCARY iterators,
then users will rely on that, even if unintentionally. Leaving that case
unspecified or undefined in the standard doesn't prevent users from relying
on it. It doesn't seem to serve any benefit to pretend it doesn't work when
it actually does on some implementations.
N.B. Libstdc++ associative container iterators are SCARY by default,
but non-SCARY when -D_GLIBCXX_DEBUG
is used to enable Debug Mode
(see Bug 62169).
I believe libc++ iterators are SCARY even when
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG
is used,
and MSVC STL iterators are SCARY even when /D_ITERATOR_DEBUG_LEVEL
is used.
[2024-12-09; Reflector poll]
Set status to Tentatively Ready after eight votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N4993.
Modify 23.2.7.1 [associative.reqmts.general] as indicated:
a.merge(a2)
-112- Result:
void
-113- Preconditions:
a.get_allocator() == a2.get_allocator()
istrue
.-114- Effects: Attempts to extract each element in
a2
and insert it intoa
using the comparison object ofa
. In containers with unique keys, if there is an element ina
with key equivalent to the key of an element froma2
, then that element is not extracted froma2
.-115- Postconditions: Pointers and references to the transferred elements of
a2
refer to those same elements but as members ofa
. Ifa.begin()
anda2.begin()
have the same type, iteratorsIteratorsreferring to the transferred elements will continue to refer to their elements, but they now behave as iterators intoa
, not intoa2
.-116- Throws: Nothing unless the comparison objects throws.
-117- Complexity: N log(
a.size()
+N), where N has the valuea2.size()
.
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:
constexpr
explicititerator(iterator_t<Base> current, difference_type pos); // exposition only
Modify the detailed description in 25.7.24.3 [range.enumerate.iterator] as shown:
constexpr
explicititerator(iterator_t<Base> current, difference_type pos);-2- Effects: Initializes
current_
withstd::move(current)
andpos_
withpos
.
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.
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.5 [range.refinements] as indicated:
-6- The
viewable_range
concept specifies the requirements of arange
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>))));
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
adaptor
accepts more than one argument, then letrange
be an expression such thatdecltype((range))
models, let
viewable_rangerangeargs...
be arguments such thatadaptor(range, args...)
is a well-formed expression as specified in the rest of subclause 25.7 [range.adaptors], and letBoundArgs
be a pack that denotesdecay_t<decltype((args))>...
. The expressionadaptor(args...)
produces a range adaptor closure objectf
that is a perfect forwarding call wrapper (22.10.4 [func.require]) with the following properties: [...]
chrono::parse
uses from_stream
as a customization pointSection: 30.13 [time.parse] Status: Tentatively Ready Submitter: Jonathan Wakely Opened: 2023-07-15 Last modified: 2024-12-09
Priority: 3
View other active issues in [time.parse].
View all other issues in [time.parse].
View all issues with Tentatively Ready status.
Discussion:
30.13 [time.parse] says: "Each parse
overload specified
in this subclause calls from_stream
unqualified,
so as to enable argument dependent lookup (6.5.4 [basic.lookup.argdep])."
That name should be added to 16.4.2.2 [contents] along with
swap
,
make_error_code
, and
make_error_condition
.
We should decide whether calls to from_stream
should use normal
lookup (i.e. unqualified lookup plus ADL) or just ADL, as was done for
make_error_code
and make_error_condition
(see LWG 3629(i)).
[2023-10-30; Reflector poll]
Set priority to 3 after reflector poll.
[2024-12-02; Jonathan provides wording]
I suggest that from_stream
should only be found via ADL,
not unqualified lookup. This is consistent with what we did for
make_error_code
and make_error_condition
, and more recently for
submdspan_mapping
. I see no reason to treat from_stream
differently.
This implies that implementations might need a poison poll in std::chrono
so that unqualified lookup stops as soon as those are found.
[2024-12-09; Reflector poll]
Set status to Tentatively Ready after six votes in favour during reflector poll.
Proposed resolution:
This wording is relative to N4993.
Modify 16.4.2.2 [contents] as indicated:
-3- Whenever an unqualified name other than
swap
,make_error_code
,make_error_condition
,from_stream
, orsubmdspan_mapping
is used in the specification of a declarationD
in Clause 17 through Clause 33 or Annex D, its meaning is established as-if by performing unqualified name lookup (6.5.3 [basic.lookup.unqual]) in the context ofD
.[Note 1: Argument-dependent lookup is not performed. — end note]
Similarly, the meaning of a qualified-id is established as-if by performing qualified name lookup (6.5.5 [basic.lookup.qual]) in the context of
D
.[Example 1: The reference to
is_array_v
in the specification ofstd::to_array
(23.3.3.6 [array.creation]) refers to::std::is_array_v
. — end example][Note 2: Operators in expressions (12.2.2.3 [over.match.oper]) are not so constrained; see 16.4.6.4 [global.functions]. — end note]
The meaning of the unqualified name
swap
is established in an overload resolution context for swappable values (16.4.4.3 [swappable.requirements]). The meanings of the unqualified namesmake_error_code
,make_error_condition
,from_stream
, andsubmdspan_mapping
are established as-if by performing argument-dependent lookup (6.5.4 [basic.lookup.argdep]).
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)...)
.
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.
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 ofr
in the following manner:
(2.1) — If
C
does not satisfyinput_range
orconvertible_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_category
is 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_category
is 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
, whereX
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.
Modify 32.5.4 [atomics.order] as indicated:
-10- Atomic read-modify-write operations shall always read the
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.-11- Implementations should make atomic stores visible to atomic loads within a reasonable amount of time.
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.
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 expressionR
such thatdecltype((R))
modelsrange
, the following expressions are equivalent:[…]
-2- Given an object
t
of typeT
, where
(2.1) —
t
is a unary function object that accepts a range argument and returns a cv-unqualified class object,[…]
then the implementation ensures that
t
is a range adaptor closure object.
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.
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>
istrue
if and only ifT
has exactly one public base classview_interface<T
U>for some typeandU
T
has no base classes of typeview_interface<U
for any other typeV>U
.V
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.
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.
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:
!empty()
istrue
.-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()));
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.
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;
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 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.
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> 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
U
bedecltype(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> 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
U
bedecay_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 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
U
bedecltype(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.