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?
This follows the Elements of Programming design philosophy of how types should behave. If you've heard the phrase "do as the
int
s do", that is that philosophy -- types should beRegular
. And the EoP definition of Regular is:which translates to real C++20 concepts as:
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 requireSemiregular
- notRegular
. 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.
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 anistream
using the>>
operator without default constructingT
(or otherwise being given a liveT
). 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.