Without getting into the general exceptions vs error codes discussion, what do you think are the downsides of using std::pair
or std:tuple
for returning multiple values, namely the function's return value AND the error/success code, similar to how many Go developers apparently do error handling?
This approach obviously has the advantage of not having to use out parameters for the function return value or error code (depending on which way around you prefer it).
This "idiom" is good because both the type and succes indicator are return values of the function. Failure might not be exceptional, so exceptions are inappropriate sometimes.
The downside however is that you have to split out the two return types. This can be ugly; using std::tie
helps but you are unable to construct from multiple return.
bool success;
std::string value;
std::tie(success, value)=try_my_func();
This is quite verbose.
Second, if one of the types is "optional" depending on the value of another element in the tuple then it still has to be constructed which for some types is still very wasteful.
If you are using the idiom a lot, consider instead using something like the boost::optional
type. This is close to the haskel maybe than go's multiple return.
http://www.boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html
For this purpose, in most cases I use an own wrapper type which introduces some syntactic sugar. Let's see an example:
template <class T>
struct Result
{
public:
enum Status {
Success,
Error
};
// Feel free to change the default behavior... I use implicit
// constructors for type T for syntactic sugar in return statements.
Result(T resultValue) : s(Success), v(resultValue) {}
explicit Result(Status status, std::string errMsg = std::string()) : s(status), v(), errMsg(errMsg) {}
Result() : s(Error), v() {} // Error without message
// Explicit error with message
static Result error(std::string errMsg) { return Result(Error, errMsg); }
// Implicit conversion to type T
operator T() const { return v; }
// Explicit conversion to type T
T value() const { return v; }
Status status() const { return s; }
bool isError() const { return s == Error; }
bool isSuccessful() const { return s == Success; }
std::string errorMessage() const { return errMsg; }
private:
T v;
Status s;
// if you want to provide error messages:
std::string errMsg;
};
Then, simply use this class as the return value in your methods which can return errors:
Result<int> fac(int n) {
if(n < 0)
return Result<int>::error("n has to be greater or equal zero!");
if(n == 0)
return 1;
if(n > 0)
return n * fac(n-1); // gets automatically converted to int
}
Of course this implementation of the factorial function is horrible, but demonstrates the conversion without bothering about the error-extended return type we use.
Example usage:
int main() {
for(int i = -3; i < 4; ++i)
{
Result<int> r = fac(i);
std::cout << i << " | ";
std::cout << (r.isSuccessful() ? "ok" : "error") << " | ";
if(r.isSuccessful())
std::cout << r.value();
else
std::cout << r.errorMessage();
std::cout << std::endl;
}
}
Output:
-3 | error | n has to be greater or equal zero!
-2 | error | n has to be greater or equal zero!
-1 | error | n has to be greater or equal zero!
0 | ok | 1
1 | ok | 1
2 | ok | 2
3 | ok | 6
One big advantage of the custom type is that you can insert some control ensuring that the client code always checks for errors before accessing the actual value and only accesses the value if it was successful respectively the error message if it wasn't. For this, we can extend the class by the following:
struct Result
{
public:
// in all constructors, add:
Result(...) : ..., checked(false) {...}
// in the error checker methods, add: (and drop const-ness)
bool is...() { checked = true; return ... }
// rewrite the value conversion as follows:
operator T() const { std::assert(checked && isSuccessful()); return v; }
T value() const { std::assert(checked && isSuccessful()); return v; }
// rewrite the errorMessage-getter as follows:
std::string errorMessage() const { std::assert(checked && isError()); return errMsg; }
private:
...
bool checked;
};
You might want to make the class definition depending on the build mode (debug build / release build).
Note that the example has to be rewritten as follows:
Result<int> fac(int n) {
if(n < 0)
return Result<int>::error("n has to be greater or equal zero!");
if(n == 0)
return 1;
if(n > 0) {
Result<int> r = fac(n - 1);
if(r.isError()) return r; // propagate error (similar to exceptions)
return n * r; // r gets automatically converted to int
}
}
The main-code from above is still valid, as it already did error-checking before accessing the value / the error message.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With