concat_view::iterator
's iter_swap
is overconstrainedSection: 25.7.18.3 [range.concat.iterator] Status: New Submitter: Hewill Kang Opened: 2024-04-30 Last modified: 2024-06-24
Priority: 3
View other active issues in [range.concat.iterator].
View all other issues in [range.concat.iterator].
View all issues with New status.
Discussion:
When the underlying iterator of two concat_view::iterator
s are of different types,
their iter_swap
will dispatch ranges::swap
to swap elements, which is reflected
in its constraints of swappable_with<iter_reference_t<iterator>,
iter_reference_t<iterator>>
.
ranges::swap
will never
be invoked, making checking for swappable_with
unnecessary in this case.
This results in the current wording rejecting the following:
struct I {
using value_type = int;
using difference_type = int;
std::reference_wrapper<int> operator*() const;
I& operator++();
void operator++(int);
bool operator==(const I&) const;
friend void iter_swap(const I&, const I&); // custom iter_swap
};
static_assert(std::indirectly_swappable<I>);
int main() {
std::ranges::subrange<I, I> s1, s2;
std::ranges::swap_ranges(s1, s2);
std::ranges::concat_view c1{s1}, c2{s2};
std::ranges::swap_ranges(c1, c2); // ill-formed
}
Because reference_wrapper
does not satisfy swappable_with
, concat_view::iterator
does not have a valid iter_swap
, leading to the constraints of swap_ranges
being unsatisfied.
iter_swap
specialization
for concat_view::iterator
is reasonable.
[2024-06-24; Reflector poll]
Set priority to 3 after reflector poll. An extreme corner case, probably not worth caring about.
Proposed resolution:
This wording is relative to N4981.
Modify 25.7.18.3 [range.concat.iterator] as indicated:
namespace std::ranges { template<input_range... Views> requires (view<Views> && ...) && (sizeof...(Views) > 0) && concatable<Views...> template<bool Const> class concat_view<Views...>::iterator { public: using iterator_category = see below; // not always present. using iterator_concept = see below; using value_type = concat-value-t<maybe-const<Const, Views>...>; using difference_type = common_type_t<range_difference_t<maybe-const<Const, Views>>...>; private: static constexpr bool concat-is-homogeneous = see below; // exposition only using base-iter = // exposition only variant<iterator_t<maybe-const<Const, Views>>...>; […] friend constexpr void iter_swap(const iterator& x, const iterator& y) noexcept(see below) requires concat-is-homogeneous && indirectly_swappable<iterator_t<maybe-const<Const, Views...[0]>>>; friend constexpr void iter_swap(const iterator& x, const iterator& y) noexcept(see below) requiressee below(!concat-is-homogeneous) && swappable_with<iter_reference_t<iterator>, iter_reference_t<iterator>> && (... && indirectly_swappable<iterator_t<maybe-const<Const, Views>>>); } […] }-?-
-1-concat-is-homogeneous
istrue
if and only if the pack of typesiterator_t<maybe-const<Const, Views>>...
are all the same.iterator::iterator_concept
is defined as follows: […]friend constexpr void iter_swap(const iterator& x, const iterator& y) noexcept(see below) requires concat-is-homogeneous && indirectly_swappable<iterator_t<maybe-const<Const, Views...[0]>>>;-?- Preconditions:
-?- Effects: Equivalent to:x.it_.valueless_by_exception()
andy.it_.valueless_by_exception()
are eachfalse
.std::visit(ranges::iter_swap, x.it_, y.it_);-?- Remarks: The exception specification is equivalent to
noexcept(ranges::iter_swap(std::get<0>(x.it_), std::get<0>(y.it_)))friend constexpr void iter_swap(const iterator& x, const iterator& y) noexcept(see below) requiressee below(!concat-is-homogeneous) && swappable_with<iter_reference_t<iterator>, iter_reference_t<iterator>> && (... && indirectly_swappable<iterator_t<maybe-const<Const, Views>>>);-42- Preconditions:
-43- Effects: Equivalent to:x.it_.valueless_by_exception()
andy.it_.valueless_by_exception()
are eachfalse
.std::visit([&](const auto& it1, const auto& it2) { if constexpr (is_same_v<decltype(it1), decltype(it2)>) { ranges::iter_swap(it1, it2); } else { ranges::swap(*x, *y); } }, x.it_, y.it_);-44- Remarks: The exception specification is equivalent to
(noexcept(ranges::swap(*x, *y)) && ... && noexcept(ranges::iter_swap(its, its)))where
its
is a pack of lvalues of typeconst iterator_t<maybe-const<Const, Views>>
respectively.
The expression in the requires-clause is equivalent toswappable_with<iter_reference_t<iterator>, iter_reference_t<iterator>> && (... && indirectly_swappable<iterator_t<maybe-const<Const, Views>>>)