3714. Non-single-argument constructors for range adaptors should not be explicit

Section: 25.7.26.2 [range.zip.transform.view], 25.7.28.2 [range.adjacent.transform.view], 25.7.29.2 [range.chunk.view.input], 25.7.29.6 [range.chunk.view.fwd], 25.7.30.2 [range.slide.view], 25.7.31.2 [range.chunk.by.view] Status: NAD Submitter: Hewill Kang Opened: 2022-06-10 Last modified: 2024-06-24

Priority: 4

View all issues with NAD status.

Discussion:

All C++20 range adaptors' non-single-argument constructors are not declared as explicit, which makes the following initialization well-formed:

std::vector v{42};
std::ranges::take_view r1 = {v, 1};
std::ranges::filter_view r2 = {v, [](int) { return true; }};

However, the non-single-argument constructors of C++23 range adaptors, except for join_with_view, are all explicit, which makes us no longer able to

std::ranges::chunk_view r1 = {v, 1}; // ill-formed
std::ranges::chunk_by_view r2 = {v, [](int, int) { return true; }}; // ill-formed

This seems unnecessary since I don't see the observable benefit of preventing this. In the standard, non-single-argument constructors are rarely specified as explicit unless it is really undesirable, I think the above initialization is what the user expects since it's clearly intentional, and I don't see any good reason to reject it from C++23.

[2022-06-11; Daniel comments]

Another possible candidate could be 25.7.11.3 [range.take.while.sentinel]'s

constexpr explicit sentinel(sentinel_t<Base> end, const Pred* pred);

[2022-06-21; Reflector poll]

Set priority to 4 after reflector poll. Send to LEWG.

[2023-01-24; LEWG in Kona]

Use alternative approach in P2711 instead.

[2024-06-24 Status changed: Tentatively NAD → NAD.]

P2711R1 was approved in February 2023, confirming that these constructors should be explicit.

Proposed resolution:

This wording is relative to N4910.

  1. Modify 25.7.26.2 [range.zip.transform.view] as indicated:

    namespace std::ranges {
      template<copy_constructible F, input_range... Views>
        requires (view<Views> && ...) && (sizeof...(Views) > 0) && is_object_v<F> &&
                  regular_invocable<F&, range_reference_t<Views>...> &&
                  can-reference<invoke_result_t<F&, range_reference_t<Views>...>>
      class zip_transform_view : public view_interface<zip_transform_view<F, Views...>> {
        copyable-box<F> fun_;                   // exposition only
        zip_view<Views...> zip_;                // exposition only
        […]
      public:
        zip_transform_view() = default;
    
        constexpr explicit zip_transform_view(F fun, Views... views);
        […]
      };
      […]
    }
    
    constexpr explicit zip_transform_view(F fun, Views... views);
    

    -1- Effects: Initializes fun_ with std::move(fun) and zip_ with std::move(views)....

  2. Modify 25.7.28.2 [range.adjacent.transform.view] as indicated:

    namespace std::ranges {
      template<forward_range V, copy_constructible F, size_t N>
        requires view<V> && (N > 0) && is_object_v<F> &&
                 regular_invocable<F&, REPEAT(range_reference_t<V>, N)...> &&
                 can-reference<invoke_result_t<F&, REPEAT(range_reference_t<V>, N)...>>
      class adjacent_transform_view : public view_interface<adjacent_transform_view<V, F, N>> {
        copyable-box<F> fun_;                       // exposition only
        adjacent_view<V, N> inner_;                 // exposition only
        […]
      public:
        adjacent_transform_view() = default;
        constexpr explicit adjacent_transform_view(V base, F fun);
        […]
      };
      […]
    }
    
    constexpr explicit adjacent_transform_view(V base, F fun);
    

    -1- Effects: Initializes fun_ with std::move(fun) and inner_ with std::move(base).

  3. Modify 25.7.29.2 [range.chunk.view.input] as indicated:

    namespace std::ranges {
      […]
      template<view V>
        requires input_range<V>
      class chunk_view : public view_interface<chunk_view<V>> {
        V base_ = V();                                        // exposition only
        range_difference_t<V> n_ = 0;                         // exposition only
        […]
      public:
        chunk_view() requires default_initializable<V> = default;
        constexpr explicit chunk_view(V base, range_difference_t<V> n);
        […]
      };
      […]
    }
    
    constexpr explicit chunk_view(V base, range_difference_t<V> n);
    

    -1- Preconditions: n > 0 is true.

    -2- Effects: Initializes base_ with std::move(base) and n_ with n.

  4. Modify 25.7.29.6 [range.chunk.view.fwd] as indicated:

    namespace std::ranges {
      template<view V>
        requires forward_range<V>
      class chunk_view<V> : public view_interface<chunk_view<V>> {
        V base_ = V();                      // exposition only
        range_difference_t<V> n_ = 0;       // exposition only
        […]
      public:
        chunk_view() requires default_initializable<V> = default;
        constexpr explicit chunk_view(V base, range_difference_t<V> n);
        […]
      };
    }
    
    constexpr explicit chunk_view(V base, range_difference_t<V> n);
    

    -1- Preconditions: n > 0 is true.

    -2- Effects: Initializes base_ with std::move(base) and n_ with n.

  5. Modify 25.7.30.2 [range.slide.view] as indicated:

    namespace std::ranges {
      […]
      template<forward_range V>
        requires view<V>
      class slide_view : public view_interface<slide_view<V>> {
        V base_ = V();                      // exposition only
        range_difference_t<V> n_ = 0;       // exposition only
        […]
      public:
        slide_view() requires default_initializable<V> = default;
        constexpr explicit slide_view(V base, range_difference_t<V> n);
        […]
      };
      […]
    }
    
    constexpr explicit slide_view(V base, range_difference_t<V> n);
    

    -1- Effects: Initializes base_ with std::move(base) and n_ with n.

  6. Modify 25.7.31.2 [range.chunk.by.view] as indicated:

    namespace std::ranges {
      template<forward_range V, indirect_binary_predicate<iterator_t<V>, iterator_t<V>> Pred>
        requires view<V> && is_object_v<Pred>
      class chunk_by_view : public view_interface<chunk_by_view<V, Pred>> {
        V base_ = V();                                // exposition only
        copyable-box<Pred> pred_ = Pred();            // exposition only
        […]
      public:
        chunk_by_view() requires default_initializable<V> && default_initializable<Pred> = default;
        constexpr explicit chunk_by_view(V base, Pred pred);
        […]
      };
      […]
    }
    
    constexpr explicit chunk_by_view(V base, Pred pred);
    

    -1- Effects: Initializes base_ with std::move(base) and pred_ with std::move(pred).