Title
View converting constructors can cause constraint recursion and are unneeded
Status
c++20
Section
[range.filter.view][range.transform.view][range.take.view] [range.join.view][range.split.view][range.reverse.view]
Submitter
Eric Niebler

Created on 2019-09-09.00:00:00 last changed 38 months ago

Messages

Date: 2020-01-05.16:32:39

Proposed resolution:

This wording is relative to N4830.

  1. Modify [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 [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 [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 [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 [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 [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)).

Date: 2020-01-05.16:32:39

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

Date: 2019-10-07.02:21:30

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

Date: 2019-09-09.00:00:00

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:

  • decay_t<R>

  • ref_view<remove_reference_t<R>>

  • subrange<iterator_t<R>, sentinel_t<R>, [sized?]>

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.

History
Date User Action Args
2021-02-25 10:48:01adminsetstatus: wp -> c++20
2020-02-24 16:02:59adminsetstatus: voting -> wp
2020-01-17 04:54:50adminsetstatus: ready -> voting
2020-01-05 16:32:39adminsetmessages: + msg10913
2020-01-05 16:32:39adminsetstatus: new -> ready
2019-10-07 02:21:30adminsetmessages: + msg10683
2019-09-15 14:24:35adminsetmessages: + msg10631
2019-09-09 00:00:00admincreate