3

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:

  1. How should I design a type that has no "natural (as in value)" defaults? Should I provide a default constructor or not?

  2. 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 makes optional<T> useless in this case.

  3. 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?

  4. 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?

djs
  • 1,660
  • 1
  • 17
  • 35
Germán Diago
  • 7,473
  • 1
  • 36
  • 59
  • Actually, I would be strongly interested in Stepanov's and Sean Parent's opinion on these matters. Any material? P.S.: Didn't finish reading elements of programming, maybe the reply is there. – Germán Diago Oct 29 '14 at 06:17

1 Answers1

1

(Note: a problem with lots of questions in one question is that some parts of it can be duplicate. Best to ask smaller questions, and check each for prior posts. "One question per question" is a good policy; easier said than done sometimes, I guess.)

  1. Why were types such as std::function 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.

See Why do std::function instances have a default constructor?

  1. How should I design a type that has no "natural (as in value)" defaults? Should I provide a default constructor or not?

Default constructors for types that have a tough time meaningfully defining themselves without some kind of data is how a lot of classes implement a null value. Is optional a better choice? I usually think so, but I'm assuming you're aware that std::optional was voted out of C++14. Even if it were the perfect answer it can't be everyone's answer...it's not soup yet.

It will always add some overhead to do runtime tracking of if the value is bound or not. Perhaps not a lot of overhead. But when you are using a language whose raison d'etre is to allow abstraction while still letting you shoot yourself in the foot as close to the metal as you want...shaving off a byte per value in a giant vector can be important.

So even if optional<T> semantics and compile-time checking were perfect, you still might face a scenario where it's advantageous to scrap it and allow your type to encode its own nullity. Gotta push those pixels or polygons or packets or... pfafftowns.

  1. In the case I choose to provide a default constructor, how does optional fit into all this puzzle? My view is that making a type that is not "naturally" default constructible provide a default constructor makes optional useless in this case.

  2. Should optional 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 (except float and double NaN I guess).

In my own case, I found myself wanting to distinguish at compile-time checking between routines that could handle null pointers and those which could not. But suddenly an optional<pointer> offered this situation of either the optional being unbound, being bound to a null pointer, and being bound to a non-null pointer. The compile-time sanity check seeming less the win it had.

So how about optional references? They're controversial to the point that last I heard they're one of the sticking points in the set of things that delayed std::optional from C++14. Which was a bit annoying after I'd converted my optional pointers to optional references. :-/

I had a vague idea to write a book about "pathological C++" where you pick some idea and start taking it to its logical conclusions. optional<T> was one kick I got on and going with essentially the principles you identify. Remove the possibility of "nullity" from being encoded in the type itself, and then suddenly you can get the compiler doing the type-checking for whether a given bit of code is prepared to expect a null or not.

(These days I tend toward suspecting if you get very hung up on this kind of "pathological C++" you'll wind up reinventing Haskell. :-/ See the popular Data.Maybe monad.)

Community
  • 1
  • 1
  • I think that these questions are so closely related that if I split them I will loose the point, actually. You must see it as an "all related thing" -> default construct, optional, regularity and tradeoffs. – Germán Diago Oct 29 '14 at 04:57
  • @GermánDiago Sure...we can see all programming as being related, so everything on StackOverflow points to *one big question*. That's why the challenge in Q&A is to break that *one big question* down into independently reusable pieces of institutional knowledge. Much like the challenge in software. *"The essence of architecture is the suppression of information not necessary to the task at hand, and so it is somehow fitting that the very nature of architecture is such that it never presents its whole self to us but only a facet or two at a time."* :-) – HostileFork says dont trust SE Oct 29 '14 at 05:08
  • Thanks for your advice. I have the strong opinion, that, in this case, it makes sense as it is now. – Germán Diago Oct 29 '14 at 06:01
  • 1
    @GermánDiago Consider also my strong opinion, which is if you find this kind of issue impassioning you vs. being kind of a distraction...you might sit down with [Learn You a Haskell](http://learnyouahaskell.com/chapters) and [Typeclassopedia](http://www.haskell.org/haskellwiki/Typeclassopedia) and realize that C++ isn't grinding this particular axe, except as a hobby on a whim. If you're pushing C++ too hard here you either need to think differently about what your priorities are, just think less in general, or switch languages. :-) – HostileFork says dont trust SE Oct 29 '14 at 06:06
  • I do like Haskell, but I find it akward to code in only-functional ways. Though, I admit functional is powerful. On the other hand, the ecosystem/community for c/c++ is so huge that it is almost unbeatable, library-wise :) – Germán Diago Oct 29 '14 at 06:14
  • BTW I took some Haskell before. Conceptually is the most powerful language I have seen. But I also need USABLE contiguos vectors I/O and some mutability for sane coding. I know about monads. They are so powerful... and... abstract. – Germán Diago Oct 29 '14 at 06:37