Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is ranges::ostream_iterator default-constructible?

This question follows a discussion in the comments here.

In Eric Niebler's ranges-v3 library (which is sort-of becoming part of the standard for C++20), ranges::ostream_iterator is default-constructible - without an ostream.

How come?

I thought that "dummy" construction with effective construction later is an anti-pattern in C++, a wart we are gradually getting rid of. std::ostream iterator can only be constructed with a stream (for now - before C++20). And it's not as though we can do anything with the default-constructed range::ostream_iterator... So, what's the deal?

like image 626
einpoklum Avatar asked May 10 '19 21:05

einpoklum


Video Answer


2 Answers

There are a lot of things in C++ where a non-default-constructible type is simply not workable. Here's a really simple example: extract a type T from an istream using the >> operator without default constructing T (or otherwise being given a live T). You can't, because the interface itself requires that one exists. The interface is designed to assume that you can always construct an object of an extractable type.

And if you're not given an object to work with, that means default constructing it.

This seems like a cherry picked example, but it isn't. It is a semi-frequent occurrence that in generic code, you sometimes need to just create a T so that you can fill bits of it in later.

However much we would like to say that objects should only be default constructible if it is meaningful for them to be in such a state, it simply is not a practical reality. Sometimes, you just have to create an object now and get it filled in with a useful value later.

As such, the Ranges v3 library enshrines this requirement in the basic and frequently used concept SemiRegular. That concept represents some of the more basic aspects of manipulation for objects: I can make one, and I can assign it. Iterators are required to follow that concept.


It should also be noted that, in C++20, ostream_iterator gains a default constructor.

like image 183
Nicol Bolas Avatar answered Nov 08 '22 02:11

Nicol Bolas


As an update to this, P2325R3 was just adopted which makes std::ostream_iterator no longer default constructible (it was briefly made so in C++20).


This follows the Elements of Programming design philosophy of how types should behave. If you've heard the phrase "do as the ints do", that is that philosophy -- types should be Regular. And the EoP definition of Regular is:

T’s computational basis includes equality, assignment, destructor, default constructor, copy constructor, total ordering (or default total ordering) and underlying type

which translates to real C++20 concepts as:

template<class T>
  concept Movable = is_object_v<T> && MoveConstructible<T> && Assignable<T&, T>
    && Swappable<T>;
template<class T>
  concept Copyable = CopyConstructible<T> && Movable<T> && Assignable<T&, const T&>;
template<class T>
  concept Semiregular = Copyable<T> && DefaultConstructible<T>;
template<class T>
  concept Regular = Semiregular<T> && EqualityComparable<T>;

We've lost the total ordering part in favor of simply EqualityComparable, and even then a lot of the library requirements via Ranges actually only require Semiregular - not Regular. But still, this is the foundation of the idea.

Note that if a type is movable, it already kind of makes sense for it to be default constructible. The moved-from state is very conceptually similar to a default-constructed state. Can't do much from there, but it's a state.

like image 27
Barry Avatar answered Nov 08 '22 03:11

Barry