A really common first run-in with the kinds of problems that void can cause is when you try to write that first benchmarking function (personally, my first non-trivial case was a result of fulfilling promises). All you want to do is time some arbitrary function and log how long it takes. No problem:

This basically does what we want. We take two timestamps around invoking our function (using std::invoke because of course pointers to members aren’t callable…), log the delta in units of seconds, and just return the result back to the caller. And this works for all functions (that return a moveable type) except those that return void — which give us a compile error pointing to the result declaration with:

error: 'void result' has incomplete type

auto result = std::invoke(/* ... */)

^~~~~~

So, what do we do now? One solution that I’m immediately rejecting is to provide an overload of timeit() for those functions that return void , duplicating the entire body. This is only mildly frustrating in this case (it’s only a 10 line function after all), but it’s a complete non-starter in general — I am not going to duplicate every function template I write that accepts arbitrary callables.

One clever solution to this particular problem (and I am using “clever” here in its typical connotation as being complimentary) is to reorganize the function such that all the work after the call gets thrown into a scope guard:

This works. And if you think about it, it’s kind of strange that it does work. Well, first, there’s the putting all of this work in a scope guard to take advantage of the fact that local variables’ (such as the one implicitly created by this macro) destructors are sequenced after the return statement, and so this does in fact time the function. That’s… okay, maybe that’s a little strange. But the real strange part here is the fact that this very much looks like we’re returning an object of type void , which is what caused us problems in our first version. The language doesn’t really work in that way, there is no void object, we just get a special rule that says that this works for void .

Cool.