Since C++23, view
s are no longer required to be default_constructible
. For range adaptors such as views::filter
and views::transform
, their default constructor is redefined as:
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>> {
private:
V base_ = V(); // exposition only
copyable-box<Pred> pred_; // exposition only
public:
filter_view() requires default_initializable<V> && default_initializable<Pred> = default;
};
And because the default constructor of ref_view
has been deleted in p2325r3, this indicates that range adapters applied into lvalue range
s such as std::vector
are no longer default_constructible
:
std::vector v{1, 2, 3};
auto r = v | std::views::filter([](auto) { return true; });
decltype(r){}; // ok in C++20, error in C++23
Only when range adapters are applied into default_constructible
view
s such as std::string_view
or views::iota(0)
, the returned views can be default_constructible
:
auto r = std::views::iota(0) | std::views::filter([](auto) { return true; });
decltype(r){}; // ok in C++20 and C++23
But regarding these cases, I really can’t think of the use cases to make these view
s default_constructible
even if that is feasible. If we construct a view
by default, it means that we construct an empty view
, and it is obviously meaningless to apply range adaptors to the empty view
s:
constexpr auto r = std::views::iota(0, 10)
| std::views::transform([](auto x) { return x; });
static_assert(!std::ranges::empty(r));
// views::iota is default-constructible, so r is also default-constructible
static_assert( std::ranges::empty(decltype(r){}));
In my opinion, the default constructor of range adaptors in C++20 is just to satisfy the view
, since the view
is no longer required to be default_constructible
in C++23, there is no need for the default constructors of these adapters to existing.
Why is the default constructor of these range adaptors not deleted in C++23 but make into a constraint function? Is there really a case that requires it to be default_constructible
? What are the considerations behind this?