Relaxing range adaptors to allow for move only types (original) (raw)
1. Introduction
Currently, many range adaptors require that the user-provided types they store must be copy constructible, which is also required by the assignment wrapper they use, copyable-box
. This is a consequence of [P1456R1] and [P2325R3] not being integrated together so far. The author believes that this paper is a logical next step from these two papers.
This paper exists, because while writing a paper proposing views::repeat
(P2474), the author had a desire to make it support move-only types, and needed the tools to specify it. He tried to use the approach in [P2483R0], but has found that approach to not solve the problem correctly.
2. Revision history
2.1. R1
Apply the SG10 recommendation for handling the feature test macro for this change.
Adjust the wording changes to P2474, to align with the wording of a new revision of that paper.
2.2. R0
Initial revision.
3. Design
Similarly to how [P2325R3] turned semiregular-box
into copyable-box
, this paper proposes to turn copyable-box
into movable-box
. This name is probably not ideal, because it still turns types that happen to be copy constructible into copyable types, but it follows from the prior changes to the wrapper.
The reason why the approach in [P2483R0] is incorrect is that it does not handle types that are copy constructible, but not copy assignable: if a type is copyable, then it works with the wrapper, but if it misses copy assignment, then it does not. The approach taken in this paper solves this issue.
[P2483R0] also suggests future work to relax the requirements on the predicate types stored by standard views. This paper does not perform this relaxation, as the copy constructibility requirement is enshrined in the indirect callable concepts ([indirectcallable.indirectinvocable]). Thus, while this paper modifies the views that currently use copyable-box
for user provided predicates, it only does so to apply the rename of the exposition-only type to movable-box
; it does not change any of the constraints on those views. It does, however, relax the requirements on invocables accepted by the transform family of views, because those are not constrained using the indirect callable concepts.
In effect, the only views whose constraints are changed by this paper are:
single_view
,transform_view
,zip_transform_view
, andadjacent_transform_view
.
Additionally, this paper proposes to modify the changes in P2474 in this same way. If both this paper and P2474 are accepted, we should make views::repeat
have the same constraints as views::single
.
3.1. ABI compatibility
The author is not aware of any ABI concerns of this proposal; the question of whether changing constraints on a primary class template has been run by the ABI Review Group, and the responses indicated that constraints are only mangled in for overloadable templates on the major implementations of C++.
3.2. Feature test macro
After asking for SG10’s opinion on the topic on the group’s mailing list, this paper proposes to not introduce a new feature test macro for the proposed feature, and rather increment the value of __cpp_lib_ranges
- which is consistent with how prior changes of similar nature have been handled.
3.3. Implementation experience
The author has implemented the changes to single_view
and transform_view
in CMCSTL2. The changes to the box type were not necessary. CMCSTL2 still contains a semiregular_box
, from before the change to copyable-box
, but its semiregular box is relaxed to allow for move-only types. Thanks to that, the entire change is replacing "copy" with "move" in the two views mentioned earlier (CMCSTL2 does not include zip_transform_view
and adjacent_transform_view
). All the preexisting tests pass with the changes.
4. Wording
4.1. Header <ranges>
synopsis
Modify Header <ranges>
synopsis [ranges.syn] as follows:
// ...
// [range.single], single view
template<copy_constructiblemove_constructible T>
requires is_object_v
class single_view;
// ...
// [range.transform], transform view
template<input_range V, copy_constructiblemove_constructible F>
requires view && is_object_v &&
regular_invocable<F&, range_reference_t> &&
can-reference<invoke_result_t<F&, range_reference_t>>
class transform_view;
// ...
// [range.zip.transform], zip transform view
template<copy_constructiblemove_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...>>
class zip_transform_view;
// ...
// [range.adjacent.transform], adjacent transform view
template<forward_range V, copy_constructiblemove_constructible F, size_t N>
requires see below
class adjacent_transform_view;
// ...
4.2. Range factories
4.2.1. Class template single_view
Modify Class template single_view
[range.single.view] as follows:
namespace std::ranges {
template<copy_constructiblemove_constructible T>
requires is_object_v
class single_view : public view_interface<single_view> {
private:
copyable-boxmovable-box value_; // exposition only (see [range.copymove.wrap])
public: single_view() requires default_initializable = default; constexpr explicit single_view(const T& t) requires copy_constructible; constexpr explicit single_view(T&& t);
// ...
constexpr explicit single_view(const T& t) requires copy_constructible;
- Effects: Initializes
value_
witht
.
4.2.2. Class template iota_view
Do not modify iota_view
, which explicitly requires copyable
, and creates copies of the user-provided types as part of its operation.
4.3. Range adaptors
4.3.1. Copyable wrapper
Replace the section Copyable wrapper [range.copy.wrap] with a new section Movable wrapper [range.move.wrap]. The following diff can be applied to the body of [range.copy.wrap] to produce the body of the new [range.move.wrap] section:
- Many types in this subclause are specified in terms of an exposition-only class template
copyable-boxmovable-box.copyable-boxmovable-box<T>
behaves exactly likeoptional<T>
with the following differences:copyable-boxmovable-box<T>
constrains its type parameterT
with~~copy_constructible~~move_constructible<T> && is_object_v<T>
.- The default constructor of
copyable-boxmovable-box<T>
is equivalent to:
constexprcopyable-boxmovable-box() noexcept(is_nothrow_default_constructible_v)
requires default_initializable
:copyable-boxmovable-box{in_place} {} - If
copyable<T>
is not modeled, the copy assignment operator is equivalent to:
constexprcopyable-boxmovable-box& operator=(constcopyable-boxmovable-box& that)
noexcept(is_nothrow_copy_constructible_v)
requires copy_constructible {
if (this != addressof(that)) {
if (that) emplace(*that);
else reset();
}
return *this;
} - If
movable<T>
is not modeled, the move assignment operator is equivalent to:
constexprcopyable-boxmovable-box& operator=(copyable-boxmovable-box&& that)
noexcept(is_nothrow_move_constructible_v) {
if (this != addressof(that)) {
if (that) emplace(std::move(*that));
else reset();
}
return *this;
}
- Recommended practice:
copyable-box<T>
should store only aT
if eitherT
modelscopyable
oris_nothrow_move_constructible_v<T> && is_nothrow_copy_constructible_v<T>
istrue
.- If
copy_constructible<T>
istrue
, movable-box<T>
should store only aT
if eitherT
modelscopyable
, oris_nothrow_move_constructible_v<T> && is_nothrow_copy_constructible_v<T>
istrue
. - Otherwise, movable-box
<T>
should store only aT
if eitherT
modelsmovable
oris_nothrow_move_constructible_v<T>
istrue
.
- If
4.3.2. Class template filter_view
Modify Class template filter_view
[range.filter.view] as follows:
namespace std::ranges {
template<input_range V, indirect_unary_predicate<iterator_t> Pred>
requires view && is_object_v
class filter_view : public view_interface<filter_view<V, Pred>> {
private:
V base_ = V(); // exposition only
copyable-boxmovable-box pred_; // exposition only
// ...
4.3.3. Class template transform_view
Modify Class template transform_view
[range.transform.view] as follows:
namespace std::ranges {
template<input_range V, copy_constructiblemove_constructible F>
requires view && is_object_v &&
regular_invocable<F&, range_reference_t> &&
can-reference<invoke_result_t<F&, range_reference_t>>
class transform_view : public view_interface<transform_view<V, F>> {
private:
// [range.transform.iterator], class template transform_view::iterator
template struct iterator; // exposition only
// [range.transform.sentinel], class template transform_view::sentinel
template<bool> struct sentinel; // exposition only
V base_ = V(); // exposition only
_~~copyable-box~~movable-box_<F> fun_; // exposition only
// ...
4.3.4. Class template take_while_view
Modify Class template take_while_view
[range.take.while.view] as follows:
namespace std::ranges { template<view V, class Pred> requires input_range && is_object_v && indirect_unary_predicate<const Pred, iterator_t> class take_while_view : public view_interface<take_while_view<V, Pred>> { // [range.take.while.sentinel], class template take_while_view::sentinel template class sentinel; // exposition only
V base_ = V(); // exposition only
_~~copyable-box~~movable-box_<Pred> pred_; // exposition only
// ...
4.3.5. Class template drop_while_view
namespace std::ranges { template<view V, class Pred> requires input_range && is_object_v && indirect_unary_predicate<const Pred, iterator_t> class drop_while_view : public view_interface<drop_while_view<V, Pred>> { public: drop_while_view() requires default_initializable && default_initializable = default; constexpr drop_while_view(V base, Pred pred);
constexpr V base() const& requires copy_constructible<V> { return base_; }
constexpr V base() && { return std::move(base_); }
constexpr const Pred& pred() const;
constexpr auto begin();
constexpr auto end() { return ranges::end(base_); }
private:
V base_ = V(); // exposition only
copyable-boxmovable-box pred_; // exposition only
// ...
4.3.6. Class template zip_transform_view
Modify Class template zip_transform_view
[range.zip.transform.view] as follows:
namespace std::ranges {
template<copy_constructiblemove_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...>>
class zip_transform_view : public view_interface<zip_transform_view<F, Views...>> {
copyable-boxmovable-box fun_; // exposition only
// ...
4.3.7. Class template adjacent_transform_view
Modify Class template adjacent_transform_view
[range.adjacent.transform.view] as follows:
namespace std::ranges {
template<forward_range V, copy_constructiblemove_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-boxmovable-box fun_; // exposition only
// ...
4.4. Feature test macro
Bump the Ranges feature-test macro in 17.3.2 [version.syn], Header <version>
synopsis, with the value selected by the editor to reflect the date of adoption of this paper:
#define __cpp_lib_ranges 202110L20XXXXL
5. Modifications to P2474
Note: these changes should be applied to the Working Draft if both this paper and P2474 are approved.
Modify Header <ranges>
synopsis [ranges.syn] as follows:
namespace std::ranges { // ...
// [range.repeat], repeat view
template<copy_constructiblemove_constructible W, semiregular Bound = unreachable_sentinel_t>
requires (is_object_v && same_as<W, remove_cv_t>
&& (is-integer-like || same_as<Bound, unreachable_sentinel_t>))
class repeat_view;
// ... }
Modify Class template repeat_view
[range.repeat.view] as follows:
namespace std::ranges {
template<copy_constructiblemove_constructible W, semiregular Bound = unreachable_sentinel_t>
requires (is_object_v && same_as<W, remove_cv_t>
&& (is-integer-like || same_as<Bound, unreachable_sentinel_t>))
class repeat_view : public view_interface<repeat_view<W, Bound>> {
private:
// [range.repeat.iterator], class range_view::iterator
struct iterator;
_~~copyable-box~~movable-box_<W> value_ = W(); // exposition only (see [range.~~copy~~move.wrap])
Bound bound_ = Bound(); // exposition only
public: repeat_view() requires default_initializable = default;
constexpr explicit repeat_view(const W & value, Bound bound = Bound())
requires copy_constructible<W>;
// ...
constexpr explicit repeat_view(const W & value, Bound bound = Bound()) requires copy_constructible;
- Effects: Initializes
value_
withvalue
andbound_
withbound
.
6. Acknowledgements
Thanks to Christopher Di Bella, Corentin Jabot, and Casey Carter for a review and comments of this paper. Thanks to Hui Xie for authoring [P2483R0].