3280. View converting constructors can cause constraint recursion and are unneeded

Section: 25.7.8.2 [range.filter.view], 25.7.9.2 [range.transform.view], 25.7.10.2 [range.take.view], 25.7.14.2 [range.join.view], 25.7.17.2 [range.split.view], 25.7.21.2 [range.reverse.view] Status: C++20 Submitter: Eric Niebler Opened: 2019-09-09 Last modified: 2021-02-25

Priority: 1

View all issues with C++20 status.

Discussion:

The following program fails to compile:

#include <ranges>

int main() {
  namespace ranges = std::ranges;
  int a[] = {1, 7, 3, 6, 5, 2, 4, 8};
  auto r0 = ranges::view::reverse(a);
  auto is_even = [](int i) { return i % 2 == 0; };
  auto r1 = ranges::view::filter(r0, is_even);
  int sum = 0;
  for (auto i : r1) {
    sum += i;
  }
  return sum - 20;
}

The problem comes from constraint recursion, caused by the following constructor:

template<viewable_range R>
  requires bidirectional_range<R> && constructible_from<V, all_view<R>>
constexpr explicit reverse_view(R&& r);

This constructor owes its existence to class template argument deduction; it is the constructor we intend to use to resolve reverse_view{r}, which (in accordance to the deduction guide) will construct an object of type reverse_view<all_view<decltype(r)>>.

However, we note that all_view<R> is always one of:

In all cases, there is a conversion from r to the destination type. As a result, the following non-template reverse_view constructor can fulfill the duty that the above constructor was meant to fulfill, and does not cause constraint recursion:

constexpr explicit reverse_view(V r);

In short, the problematic constructor can simply be removed with no negative impact on the design. And the similar constructors from the other range adaptors should similarly be stricken.

Suggested priority P1. The view types are unusable without this change.

This proposed resolution has been implemented in range-v3 and has been shipping for some time.

[2019-10 Priority set to 1 after reflector discussion]

[2019-10 Status set to ready Wednesday night discussion in Belfast.]

Proposed resolution:

This wording is relative to N4830.

  1. Modify 25.7.8.2 [range.filter.view] as indicated:

    namespace std::ranges {
      template<input_range V, indirect_unary_predicate<iterator_t<V>> Pred>
        requires view<V> && is_object_v<Pred>
      class filter_view : public view_interface<filter_view<V, Pred>> {
      […]
      public:
        filter_view() = default;
        constexpr filter_view(V base, Pred pred);
        template<input_range R>
          requires viewable_range<R> && constructible_from<V, all_view<R>>
        constexpr filter_view(R&& r, Pred pred);
        […]
      };
      […]
    }
    
    […]
    template<input_range R>
      requires viewable_range<R> && constructible_from<V, all_view<R>>
    constexpr filter_view(R&& r, Pred pred);
    

    -2- Effects: Initializes base_ with views::all(std::forward<R>(r)) and initializes pred_ with std::move(pred).

  2. Modify 25.7.9.2 [range.transform.view] as indicated:

    namespace std::ranges {
      template<input_range V, copy_constructible F>
        requires view<V> && is_object_v<F> &&
        regular_invocable<F&, range_reference_t<V>>
      class transform_view : public view_interface<transform_view<V, F>> {
      private:
        […]
      public:
        transform_view() = default;
        constexpr transform_view(V base, F fun);
        template<input_range R>
          requires viewable_range<R> && constructible_from<V, all_view<R>>
        constexpr transform_view(R&& r, F fun);
        […]
      };
      […]
    }
    
    […]
    template<input_range R>
      requires viewable_range<R> && constructible_from<V, all_view<R>>
    constexpr transform_view(R&& r, F fun);
    

    -2- Effects: Initializes base_ with views::all(std::forward<R>(r)) and fun_ with std::move(fun).

  3. Modify 25.7.10.2 [range.take.view] as indicated:

    namespace std::ranges {
      template<view V>
      class take_view : public view_interface<take_view<V>> {
      private:
        […]
      public:
        take_view() = default;
        constexpr take_view(V base, range_difference_t<V> count);
        template<viewable_range R>
          requires constructible_from<V, all_view<R>>
        constexpr take_view(R&& r, range_difference_t<V> count);
        […]
      };
    […]
    }
    
    […]
    template<viewable_range R>
      requires constructible_from<V, all_view<R>>
    constexpr take_view(R&& r, range_difference_t<V> count);
    

    -2- Effects: Initializes base_ with views::all(std::forward<R>(r)) and count_ with count.

  4. Modify 25.7.14.2 [range.join.view] as indicated:

    namespace std::ranges {
      template<input_range V>
        requires view<V> && input_range<range_reference_t<V>> &&
                 (is_reference_v<range_reference_t<V>> ||
                 view<range_value_t<V>>)
      class join_view : public view_interface<join_view<V>> {
      private:
        […]
      public:
        join_view() = default;
        constexpr explicit join_view(V base);
        template<input_range R>
          requires viewable_range<R> && constructible_from<V, all_view<R>>
        constexpr explicit join_view(R&& r);
        […]
      };
    […]
    }
    
    […]
    template<input_range R>
      requires viewable_range<R> && constructible_from<V, all_view<R>>
    constexpr explicit join_view(R&& r);
    

    -2- Effects: Initializes base_ with views::all(std::forward<R>(r)).

  5. Modify 25.7.17.2 [range.split.view] as indicated:

    namespace std::ranges {
    […]
      template<input_range V, forward_range Pattern>
        requires view<V> && view<Pattern> &&
                 indirectly_comparable<iterator_t<V>, iterator_t<Pattern>, ranges::equal_to> &&
                 (forward_range<V> || tiny-range<Pattern>)
      class split_view : public view_interface<split_view<V, Pattern>> {
      private:
        […]
      public:
        split_view() = default;
        constexpr split_view(V base, Pattern pattern);
        template<input_range R, forward_range P>
          requires constructible_from<V, all_view<R>> &&
                   constructible_from<Pattern, all_view<P>>
        constexpr split_view(R&& r, P&& p);
        […]
      };
    […]
    }
    
    […]
    template<input_range R, forward_range P>
      requires constructible_from<V, all_view<R>> &&
               constructible_from<Pattern, all_view<P>>
    constexpr split_view(R&& r, P&& p);
    

    -2- Effects: Initializes base_ with views::all(std::forward<R>(r)), and pattern_ with views::all(std::forward<P>(p)).

  6. Modify 25.7.21.2 [range.reverse.view] as indicated:

    namespace std::ranges {
      template<view V>
        requires bidirectional_range<V>
      class reverse_view : public view_interface<reverse_view<V>> {
      private:
        […]
      public:
        reverse_view() = default;
        constexpr explicit reverse_view(V r);
        template<viewable_range R>
          requires bidirectional_range<R> && constructible_from<V, all_view<R>>
        constexpr explicit reverse_view(R&& r);
        […]
      };
    […]
    }
    
    […]
    template<viewable_range R>
      requires bidirectional_range<R> && constructible_from<V, all_view<R>>
    constexpr explicit reverse_view(R&& r);
    

    -2- Effects: Initializes base_ with views::all(std::forward<R>(r)).