To hone my skills - and for the pleasure of it - I am writing a small game for my kids in modern C++ (C++11, C++14 and the part of C++17 already supported by Visual Studio), which is a nice break from my usual (enterprise) programming tasks.
I need a small interpreter for user input and of course I must handle erroneous input (think "csat magic missile" instead of "cast magic missile"). This is not exceptional, rather it is the norm that user input can be for some reason not well formed.
So I am looking for the recommended approach for handling this. I have read the C++ Core Guidelines or other questions on the site, and I have experimented with various approaches in my code.
In the end I have settled on changing all the relevant return types to something like
std::tuple<my_true_return_type, RetCode> my_function(...);
where RetCode is an enum:
enum RetCode{
SUCCESS,
WRONG_NUMBER_OF_INPUTS,
....
};
and each function call is done like:
std::tie(result, error_code) = my_function(...);
This seems more or less in line with the guidelines and I can be rather systematic with it.
My only issue with this solution is that it may happen that I am able to detect an error before even constructing my_true_return_type and in this case I have to default construct it anyway, only to discard it at the calling point.
Something like
if (an_erroneous_condition){
// I have to default-construct a return type object,
// which I didn't really need
return std::make_tupe(my_true_return_type(), FAILURE);
}
I could instead go for a Nullable return type (like the Maybe monad in Haskell), but at some cost.
In that case, I would have a relatively complex return type:
std::tuple<SomeNullableType<my_true_return_type, ...>, RetCode>
and the resulting complexity at the calling point. Furthermore there is not yet a nullable type in the standard library AFAIK, or in Boost (there has been the proposal of Boost.Outcome but is still under evolution), so I would need to find another solution, or roll my one which is fun, too, but doesn't seem stricly needed for the purpose of coding a simple game. And in all probability my solution would be half-baked, not production ready.
If I go back to the exception route I get a simplified return code and I don't have to construct any object which I don't need, but it seems to me that exceptions are to be reserved for exceptional cases and not for what I expect to be very common occur
So my question is:
- how to handle, according to current best practice, and taking full advantage of all modern C++ technique, errors which are not exceptional situations?
- if my way is reasonable, what can I do to ensure that the cost of the "useless" default constructed return type is minimized?
std::unique_ptr
, at the cost of only one extra dynamic memory allocation in case you instantiate a result. I use it all the time to signal presence/absence of a value while also managing ownership correctly (so this is better than plain pointers).std::optional
andstd::variant
(at least in VS 2017). There is a proposedstd::expected
which may behave more like what you want; Boost Outcome implements something similar to it: github.com/ned14/outcome