C++ – Should Every Assumption in Code Be Documented?

c

For instance, would this c++ function be a good idea?

void doSomething(not_nullptr<MyType> arg)
{
    // stuff
}

With not_tullptr being a template wrapper for pointers, that will throw an exception if a null value is ever assigned to it. It has the advantage that it finds errors fast, and clearly documents assumptions made directly in the function prototype.

The traditional way to do something like this would be:

void doSomething(MyType* arg)
{
    assert(arg != nullptr);
    // stuff
}

This method accomplishes the goal of finding null errors fast. But it does not itself document that assumption in the prototype.
So my question is, is the idea above a good one? It is not the standard way of doing things in the c++ and could of course be expanded to other assumptions.

Best Answer

Everything has a cost, even if it isn't measured in runtime performance.

Encoding such assumptions into the type system sounds like a good idea. But it is not without its flaws. In particular, it requires you to have and use a bunch of increasingly specific types for increasingly specific assumptions.

Let's say that you have a function that takes an array from the user and modifies the first three elements in it. Now, this function makes two assumptions: that there's actually an array and that the array is at least 3 elements long.

There are types which can encode both of these assumptions. The guideline support library type span can cover both of these. But just look at the code for that type. If it weren't available, you probably wouldn't write it yourself.

The more such assumptions you have, and the more special-case they get, the harder it is to write a type just for them. After all, span only solves this particular problem as a partial by-product of solving its real problem: having a way to represent an array of some size.

So it's a balancing act. You don't want to spend more time writing special-case types, but you do need some to cover a lot of bases. Where exactly you draw the line depends on your needs, but I don't feel that trying to encode everything into the type system is worthwhile.

Also, having contracts as part of C++, which people are working on (PDF), would be able to bridge the gap here in many of the special cases.

There is also the issue of dealing with combinations of such contracts. The not_null contract is generally a good idea, but by its very nature it cannot work with move-only types that leave the moved-from object null. Thus, not_null<unique_ptr<T>> is not a functional type.

Again, that's not to say that you shouldn't have these. But you really need to think about when it is truly appropriate to have a type encapsulate a contract and when it is not.