Suppose I want to use the new std::expected<T, E>
mechanism offered in C++23, with a function:
using E = /* domain-specific error type regarding getting Foo's */
std::expected<Foo, E> getFoo(/* whatever */);
But suppose that the code of getFoo()
may throw an exception because I'm out of memory, or an std::unordered_map
throws because I lookup a non-existent key (which I had not anticipated and cannot relate to something domain-specific) etc.
Should I expand my E
type to include all of these kinds of exceptions? Should I sort of "wrap" them within one kind of E
-type elements (perhaps using subtyping)? Should I just let those exceptions propagate as usual and reserve E
for the domain-specific errors only?
Is only one of the above courses of action idiomatic, or does it depend on the circumstances? Can you suggest some rule-of-thumb for handling such situations?
You are asking about two separate cases: out-of-memory errors and the rest.
When it comes to OOM, the only real question is this: do you intend this circumstance to be survivable for your application? For most programs, the answer is "no." As such, just letting the exception reach main
or wherever is reasonable.
When it comes to the other exceptions, those are things you have a choice in. You may not be able to convert a "non-existent key" exception into your domain error, but you can choose whether to use an API that throws such an exception. Or you can choose not to. Again, it's a question of whether you want this to be a survivable condition or not.
For example, if the user gave you a string that you expected to exist in the map, but it's not there, then that's on them and exposing that error to them should be part of your error set. But if the circumstance represents an internal programming error on your part, then termination makes sense.
This isn't the sort of thing you can theorize about. You have to apply it for the particular circumstances of each application and use case.