I'm sorry, but the final version is FAR AND AWAY the worst version of all the code examples on the page. This tiny class in the stack-allocated version is lightweight, takes up very little space, has great cache locality and performs fantastically: all operations on it will probably compile down to one or two instructions touching memory almost certainly in cache.
The heap-allocated version is so much worse! Not only do you have to pay for allocating and freeing it, every time you reference it, you're gonna have to chase a pointer. Imagine if you had 1000 of these in a vector: the stack allocated ones would be right there next to each other, incredibly cache friendly. The heap-allocated ones are gonna cache miss constantly, and there's ZERO chance the loop is going to auto-vectorize. Show this to a data-oriented design person and watch their eyes start bleeding.
And for what? So you can return null? Just return a std::optional from the factory method if you care so much (or boost::optional if you're on pre-C++17). Or use exceptions. Or just accept the fact that it might return the default value. All of those options are better than the final one. If you're choosing to program in C++, it means you should care about performance. If you don't care about performance for this kind of stuff, just use a higher-level language.
I work without exceptions, and the typical solution is to crash the whole program (using assert) if the parameter is invalid. This way you can preserve the invariant.
To avoid crashing, the caller has to validate the parameters before calling the constructor, and handle the invalid parameters at this time rather than in the exception handler, but in the end it's the same.
Crashing is fine if you are dealing with trusted input. But in the post, parameter validation was just an example. I've seen plenty of code using init methods to create network connections and the like -- and in those cases, you need proper error propagation.
Another solution if you can't use exceptions is to have something like a valid() method that the user can call to verify that the object has been correctly initialized. Unlike an init() method, calling valid() is entire optional. If the caller is sure that the constructor arguments have been correct, they don't need to call valid(). Note that you can still use assertions inside the class.
Anyways, for small classes like this one literally anything is better than implicitly forcing the user to allocate on the heap. The author should have pointed out the massive drawbacks of this approach.
In the end there's not much difference between the two. In both cases you essentially call a function/operator to verify the correctness of the object. std::optional doesn't really enforce a check, it only makes it more obvious to the user. On the other hand, returning std::optional means that it will always check the invariant, whether you need it or not, so there is a potential performance penalty. Just something to be aware of.
It really depends on the thing you’re making, but for gamedev it is often regarded as good practice to assert on error and crash the whole game, so you can explicitly find those errors right away instead of waiting for it to silently cause more subtle problems down the line.
Perhaps not that relevant in the context of C++, but I think Joe Armstrong's "let it crash" philosophy is quite interesting. It made me re-think my approach to error-handling entirely.
There seems to have been a lot of "let it crash" formerly at Google. My main experience with this has been one of annoyance, e.g:
- An old library we were using for a nice-to-have feature started crashing our server when an ACL change got rolled out. We had to disable and then permanently remove the feature.
- When reusing a server-side component in a client-side service, the implications of crashing are different. When your servers crash at Google it generally gets detected and the binary/config gets automatically rolled back and things restard cleanly. On a systemd service? Not so straightforward. Now you have to rework the component to do proper error handling, or start thinking very carefully about how your package interacts with the rest of the system.
So yeah I guess let-it-crash has its place but that place is not 'everywhere'.
Given how long and convoluted C++ template programs are, and how horrible the error messages are, I would be very curious if writing your validations in them would actually produce any additional correctness in a realistic code base (I am afraid it would add more bugs in the template logic than it would prevent).
Maybe the situation has gotten better since constexpr was introduced, though.
Not to mention, this still only works for very simple constraints; if you want to validate complex properties, doing it with types becomes almost a research problem (even something like 'this array must be sorted' encoded fully in types is going to add a lot to your program's length).
Finally, the whole rich type culture is, like you point out, missing in C++. So, even if the whole team is on board with it, you will have to work with libraries that don't prove such complex properties about the types of values they produce, putting the entire burden on your own program - either roll your own libraries or as lots of complex-type-annotated wrappers.
template< class Cmp, class Array> class Sorted;// The type of your parameter.
That type achieves nothing. std::sort doesn't return an instance of Sorted, and nothing prevents me from populating an instance of Sorted with an array that is not, in fact, sorted. So, in my opinion the following two pieces of code are perfectly equivalent:
Considering these as functions in a program that otherwise uses the mainstream C++ library ecosystem, they are equally likely to be called with an unsorted array.
In Idris or probably even in C++ with significant effort, you could create a type that can't represent an unsorted array, but that is, again, an almost research-level endeavour (and adding more complex constraints it will quickly evolve).
The problem at hand, AFAICT, is input validation in an exceptions-verboten environment, which becomes a PITA once instance construction is underway.
Inviting the question: why not move that validation to a manager class that validates the inputs and returns the desired instance, or a suitable error indication if naughty?
It’s only possible to do that if your positive integers are passed via template parameters.
All your type traits/SFAINE/concepts/static_asserts happen at compile time, you can’t use them to do any checks on the value of a type, unless it’s a value available at compile time.
The heap-allocated version is so much worse! Not only do you have to pay for allocating and freeing it, every time you reference it, you're gonna have to chase a pointer. Imagine if you had 1000 of these in a vector: the stack allocated ones would be right there next to each other, incredibly cache friendly. The heap-allocated ones are gonna cache miss constantly, and there's ZERO chance the loop is going to auto-vectorize. Show this to a data-oriented design person and watch their eyes start bleeding.
And for what? So you can return null? Just return a std::optional from the factory method if you care so much (or boost::optional if you're on pre-C++17). Or use exceptions. Or just accept the fact that it might return the default value. All of those options are better than the final one. If you're choosing to program in C++, it means you should care about performance. If you don't care about performance for this kind of stuff, just use a higher-level language.