Emboldened by two upvotes and no-one contradicting me, here's my comment to OP in a more verbose form.
So why does iota_view copy-construct its member variables instead of move-construct?
That's not what is being implied here.
Initializes value_ with value.
This only means that value
, the name of the constructor's argument, is used to initialize value_
, the member. It does not say anything about how that happens.
That's how the spec for the standard library works for the most part: it enforces only the strict minimum to ensure the desired semantics and behaviour, providing implementors with as much leeway as possible. There's nothing stopping iota_view
from using move-semantics, there's just no obligation to do so.
A quick look at MSVC's implementation shows that it does use move construction:
constexpr explicit iota_view(_Wi _Value_) noexcept(
is_nothrow_move_constructible_v<_Wi>&& is_nothrow_default_constructible_v<_Bo>) // strengthened
: _Value(_STD move(_Value_)) {}
Now, the obvious question becomes: "Why does filter_view
and transform_view
specify that std::move()
must be used?"
N.B. The following is speculation, based on the assumption that the distinction is intentional, and not an oversight one way or another.
While there might be some other reason why the wording was chosen to be so strict, I believe the difference between the class declarations gives us a reasonable explanation for the apparent inconsistency:
template<weakly_incrementable W, semiregular Bound = unreachable_sentinel_t>
requires weakly-equality-comparable-with<W, Bound> && copyable<W>
class iota_view : public view_interface<iota_view<W, Bound>> {...};
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>> {...};
template<input_range V, copy_constructible F>
requires view<V> && is_object_v<F> &&
regular_invocable<F&, range_reference_t<V>> &&
can-reference<invoke_result_t<F&, range_reference_t<V>>>
class transform_view : public view_interface<transform_view<V, F>> {
Where view is:
template<class T>
concept view =
range<T> && movable<T> && enable_view<T>;
The big difference is that iota_view
's W
is a copyable<>
, whereas filter_view
and transform_view
only expect a movable<>
for V
. So these later ones must be written in a way that accommodates move-only types (like std::unique_ptr<>
).
Since iota_view
has to work with types matching the copyable<>
concept, either copy-construction or move-construction would be acceptable from a semantics point of view. The decision is left to the implementor, even if there's no apparent reason to not use move-construction.