Issue 3714: Non-single-argument constructors for range adaptors should not be explicit (original) (raw)


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

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, { 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 && ...) && (sizeof...(Views) > 0) && is_object_v &&
    regular_invocable<F&, range_reference_t...> &&

         _can-reference_<invoke_result_t<F&, range_reference_t<Views>...>>  

class zip_transform_view : public view_interface<zip_transform_view<F, Views...>> {
copyable-box 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 && (N > 0) && is_object_v &&
regular_invocable<F&, _REPEAT_(range_reference_t, N)...> &&
can-reference<invoke_result_t<F&, _REPEAT_(range_reference_t, N)...>>
class adjacent_transform_view : public view_interface<adjacent_transform_view<V, F, N>> {
copyable-box 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
requires input_range
class chunk_view : public view_interface<chunk_view> {
V base = V(); // exposition only
range_difference_t n = 0; // exposition only
[…]
public:
chunk_view() requires default_initializable = default;
constexpr explicit chunk_view(V base, range_difference_t n);
[…]
};
[…]
}

constexpr explicit chunk_view(V base, range_difference_t 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
requires forward_range
class chunk_view : public view_interface<chunk_view> {
V base = V(); // exposition only
range_difference_t n = 0; // exposition only
[…]
public:
chunk_view() requires default_initializable = default;
constexpr explicit chunk_view(V base, range_difference_t n);
[…]
};
}

constexpr explicit chunk_view(V base, range_difference_t 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
requires view
class slide_view : public view_interface<slide_view> {
V base = V(); // exposition only
range_difference_t n = 0; // exposition only
[…]
public:
slide_view() requires default_initializable = default;
constexpr explicit slide_view(V base, range_difference_t n);
[…]
};
[…]
}

constexpr explicit slide_view(V base, range_difference_t 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, iterator_t> Pred>
requires view && is_object_v
class chunk_by_view : public view_interface<chunk_by_view<V, Pred>> {
V base = V(); // exposition only
copyable-box pred = Pred(); // exposition only
[…]
public:
chunk_by_view() requires default_initializable && default_initializable = 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).