This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4089. concat_view::iterator's iter_swap is overconstrained

Section: 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::iterators 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>>.

However, when the underlying iterators are all of the same type, 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.

This doesn't seem like it should be, and I think providing a simplified homogeneous 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.

  1. 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)
          requires see below(!concat-is-homogeneous) &&
                   swappable_with<iter_reference_t<iterator>, iter_reference_t<iterator>> &&
                   (... && indirectly_swappable<iterator_t<maybe-const<Const, Views>>>);
      }
      […]
    }
    

    -?- concat-is-homogeneous is true if and only if the pack of types iterator_t<maybe-const<Const, Views>>... are all the same.

    -1- 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: x.it_.valueless_by_exception() and y.it_.valueless_by_exception() are each false.

    -?- Effects: Equivalent to:

    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)
      requires see below(!concat-is-homogeneous) &&
               swappable_with<iter_reference_t<iterator>, iter_reference_t<iterator>> &&
               (... && indirectly_swappable<iterator_t<maybe-const<Const, Views>>>);
    

    -42- Preconditions: x.it_.valueless_by_exception() and y.it_.valueless_by_exception() are each false.

    -43- Effects: Equivalent to:

    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 type const iterator_t<maybe-const<Const, Views>> respectively.

    The expression in the requires-clause is equivalent to

    swappable_with<iter_reference_t<iterator>, iter_reference_t<iterator>> &&
    (... && indirectly_swappable<iterator_t<maybe-const<Const, Views>>>)
    
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy