I have to keep this around somewhere, because Peter Muldoon sums up quite nicely my view on exceptions in this talk given for CPPNow 2023. As a short summary would be that you should use exceptions for:
- error tracing: logging, basically, but doing it carefully, without abusing this,
- stack unwinding: a parser that would be too cumbersome to keep getting return values all over the place, and
- data passing/control flow: something which I would’ve normally suggested return codes for, but he also suggests that this should be used sparringly.
He also adds this definition of what he calls an
OmegaException which has a mutable
what() and a
data() field that is templatized. Which makes a lot of sense, but most likely I’d define a specialization of this exception for
void that would simply remove that data() field. I have taken a similar approach (except only using
std::source_location because I’m not yet 23 ready) in other places, although I never thought of attaching a data field. Perhaps it might be useful, although I’ll probably use this
Most importantly, for shallow stacks return values should be preferred, since they are considerably faster and cleaner. I’m not really happy with using a variant for return values, as I do have a huge dislike for the
std::variant type and the way to use it to begin with, but it makes sense technically. Also deep hierarchies of exceptions are frowned upon, but I guess this is really a general thing: You probably don’t need a deep hierarchy, unless you really really need it.
I’ve also learned about the std::expected which is basically a very neat way to pack a return type and an error. I noticed it when I was looking over the features of C++23, but quickly forgot about it. I’m seriously thinking about backporting this into the projects I’m working on right now (I can only use C++20, and
std::expected is C++23). with the option of simply aliasing to
std::unexpected later when we can update to 23.