Lately I see a lot of material about generic programming, and I still cannot wrap my head around one thing, when designing types. I am not sure what is the best way, let me explain.
For some types, it is natural to provide a default constructor. All the possible constructions of that type will be valid or a default makes sense, so it makes sense to provide a default. This is the case for basic types.
Later, there are some types for which default constructing them does not yield a value. For example, in the standard library we have std::function<Sig>
and std::thread
, for example. Nevertheless, they are default-constructible, even if they are not holding a value.
Later, we have the proposed optional<T>
in the standard. It makes a lot of sense to use it for basic types, since for basic types all the possible assignments represent a valid value (except double and float NaN), but I don't see how you would use it for a thread
or a std::function<Sig>
, since these types don't hold a "value" when constructed. It is AS IF these types had "optional" embedded in the type directly.
This has these drawbacks. Since there is no "natural" default (or value) construction, such as with an int:
- Now I have to litter my class with
if (valid)
in all my design and signal the error. OR - make it less safe to use if I don't do this check. Precondition -> assign before using if default-constructed.
So when I want to design a type, I always find the question: should I make it default constructible?
Pros:
- Easier to reuse in more generic contexts, because my type will more easily model SemiRegular or Regular if I add the appropiate operations.
Cons:
- Litter the whole class with
if
statements or making a contract with the user in which the class is more unsafe to use.
For example, let's say I have a class Song
with ID, artist, title, duration and year. It is very nice for the standard library to make the type default constructible. But:
- I can't just find a natural way to construct a "default Song".
- I have to litter with
if (validsong)
or make it unsafe to use.
So my questions are:
How should I design a type that has no "natural (as in value)" defaults? Should I provide a default constructor or not?
In the case I choose to provide a default constructor, how does
optional<T>
fit into all this puzzle? My view is that making a type that is not "naturally" default constructible provide a default constructor makesoptional<T>
useless in this case.Should
optional<T>
just be used for types whose domain of values is complete, meaning, I cannot assign an invalid value to its representation because all of them hold a value, such as in int?Why were types such as
std::function<Sig>
made default constructible in the first place in the standard? When constructed, it does not hold a value, so I don't see why a default constructor should be provided. You could always do:optional<function<void ()>>
, for example. Is this just a design choice and both are valid or there is one design, in this case, about choosing default vs non-default constructible superior to the other?