I have a function that returns a double. Any real number is a valid output. I'm using nan's to signal errors. I am error checking this way.
double foo();
const auto error1 = std::nan("1");
const auto error2 = std::nan("2");
const auto error3 = std::nan("3");
bool bit_equal(double d1, double d2) {
return *reinterpret_cast<long long*>(&d1) == *reinterpret_cast<long long*>(&d2);
}
const auto value = foo();
if(std::isnan(value)) {
if (bit_equal(value, error1)) /*handle error1*/;
else if (bit_equal(value, error1)) /*handle error2*/;
else if (bit_equal(value, error1)) /*handle error3*/;
else /*handle default error*/;
} else /*use value normally*/;
Alternatively, if the compiler support has caught up, I can write it this way
double foo();
constexpr auto error1 = std::nan("1");
constexpr auto error2 = std::nan("2");
constexpr auto error3 = std::nan("3");
constexpr bool bit_equal(double d1, double d2) {
return std::bit_cast<long long>(d1) == std::bit_cast<long long>(d2);
}
const auto value = foo();
if(std::isnan(value)) {
if (bit_equal(value, error1)) /*handle error1*/;
else if (bit_equal(value, error1)) /*handle error2*/;
else if (bit_equal(value, error1)) /*handle error3*/;
else /*handle default error*/;
} else /*use value normally*/;
Or even
double foo();
constexpr auto error1 = std::bit_cast<long long>(std::nan("1"));
constexpr auto error2 = std::bit_cast<long long>(std::nan("2"));
constexpr auto error3 = std::bit_cast<long long>(std::nan("3"));
const auto value = foo();
if(std::isnan(value)) {
switch(std::bit_cast<long long>(value)) {
case error1: /*handle error1*/; break;
case error1: /*handle error2*/; break;
case error1: /*handle error3*/; break;
default: /*handle default error*/;
}
} else /*use value normally*/;
I have to do this because comparing nan's with == always returns false.
Is there a standard function to perform this comparison in C++?
Are any of these 3 alternatives better than the others? Although the last option seems the most succinct, it requires me to do return std::bit_cast<double>(error1); inside foo() rather than just return error1;.
Is there a better design where I can avoid using nan as an error value?
Is there a better design where I can avoid using nan as an error value?
Yes.
Throw an exception.
Use a struct (or tuple, not really) as return value
Use an out ref parameter
Since there are better alternatives, I don't think it's worth answering question 1. and 2.
NaN error signaling
Returning NaNs as error indicators is certainly a valid design choice. If you write numeric code, I'm sure you will find many people who get annoyed when you throw exceptions on any invalid input instead of letting the error propagate through NaNs. "When in Rome, speak like the Romans", right? When in math, speak like the math.h functions ;-)
(Of course this depends on your use case and the expectations of your API users)
However, NaN payloads aren't that good. Using them as an error "hint" may work for you, so you can look at the payload in a data dump and find out where it came from. But as you certainly have noticed, there is no predefined inverse to nan(const char*). Also, NaN payloads tend not to propagate well. For example, while most math functions will return a NaN when they received a NaN input, they will give you a new one without the payload.
There is a good article by agner.org talking about this very topic: Floating point exception tracking and NAN propagation
My personal recommendation would be:
Keep returning NaN on error because it is fast to check
Keep using payloads as error hints
Use a different mechanism to signal the specific type of error
Alternative mechanisms
Options that come to mind:
Exceptions. Maybe paired up with a non-throwing variant for users that are content with just a NaN
double foo();
double foo(std::nothrow_t) noexcept;
double bar()
{
try {
double x = foo();
} except(const std::domain_error&) {
error();
}
double y;
if(std::isnan(y = foo(std::nothrow)))
error();
}
Optional error code or struct output argument: double foo(Error* error=nullptr). After the call, check for NaN. If NaN, read exact error from error struct. If the user is not interested in the exact error, they don't pass a struct to begin with
struct Error
{
int errcode;
operator bool() const noexcept
{ return errcode; }
/** throw std::domain_error with error message */
[[noreturn]] void raise() const;
void check() const
{
if(errcode)
raise();
}
}
double foo(Error* err=nullptr) noexcept;
double bar()
{
Error err;
double x;
x = foo(); // just continue on NaN
if(std::isnan(x = foo()))
return x; // abort without error explanation
if(std::isnan(x = foo(&err)))
err.raise(); // raise exception
return x;
}
std::variant<double, Error> return value. In my opinion the API is not well suited for this; too verbose. This will be fixed in C++23 with std::expected. Also less efficient because the data will likely be returned on the stack
std::pair<double, Error>. If the Error type is a simple struct without a destructor and with a maximum size of 8 byte or a primitive type (so it can be returned in a register), this will be very efficient and it is also easy to check. Building your own custom pair-like type that offers some convenience methods like get_result_or_throw_error() is also possible.
template<class T>
struct Result
{
T result;
Error err;
Result() = default;
explicit constexpr Result(T result) noexcept
: result(result),
err() // set to 0
{}
explicit constexpr Result(Error err, T result=NAN) noexcept
: result(result),
err(err)
{}
operator bool() const noexcept
{ return err; }
T check() const
{
err.check(); // may throw
return result;
}
bool unpack(T& out) const noexcept
{
if(err)
return false;
out = result;
return true;
}
};
Result<double> foo() noexcept;
double bar()
{
double x = foo().check(); // throw on error
double y = foo().result; // ignore error. Continue with NaN
}
Result<double> baz() noexcept
{
Result<double> rtrn;
double x;
if(! (rtrn = foo()).unpack(x))
return rtrn; // propagate error
rtrn.result = x + 1.; // continue operation
return rtrn;
}
Further discussion
To give a bit more of a personal opinion and also delve into a few more performance concerns:
Exceptions
Well, all the usual aspects of exception handling and when to use them apply. See for example When and how should I use exception handling?
I think at this point the general consensus on exceptions is that they should not be part of the regular control flow and should only be used for very rare, exceptional cases where you most likely want to abort the operation instead of, say mitigating the error. It is just too easy to forget catching exceptions on all call sites so they tend to travel very far up the call chain before being caught.
So their use is very situational. Do you want your users to explicitly deal with any error condition on the site where they appear? Then don't use exceptions because users of your API will definitely not be bothered using a try-except block everywhere. If you want the error to get out of the way as far as possible, use them.
As for the idea of using a second set of functions without exceptions: Well, it doesn't compose well. It's feasible for a small set of functions but do you really want to write every piece of code twice, once with and once without exceptions? Probably not.
Error output parameter
This is probably the most flexible option while remaining very efficient. Passing an additional parameter has a minor cost but it isn't too bad.
The main benefit is that this is the only option besides exceptions that allows you to compose complex error reports with dynamic memory allocation for error messages etc. without incurring extra costs in the no-error case. If you make the Result object complex enough to require a destructor, it will be passed on the stack and you need to re-read the error code and actual result value after every function call and then its destructor will run.
In contrast, the Error object will be rarely touched. Yes, its destructor will run once it goes out of scope. However, I expect that most code bases will have one error object very far up the call chain and then just pass it down and reuse that object as needed.
If you make the Error object complex you might find yourself in a situation where a caller wants the error code but not the error message, e.g. because they expect an error and want to mitigate it instead of reporting it. For this case, it might make sense to add a flag to the object to indicate that the error message should not be filled.
struct Error
{
int errcode;
bool use_message;
std::string message;
};
variant, expected
I think I've made it sufficiently clear above that I don't think std::variant has a suitable API for this task. std::expected may one day be available on every platform you target but right now it isn't and you will definitely draw the ire of your release engineers if you start using C++23 features and they have to build your code for RHEL-8 or something similarly long-lived.
Performance-wise all the points I discuss below for Result apply. In addition, the floating point result will always be returned either on the stack or in a general purpose register. Using the Result or std::pair approach will at least get double results in a floating point register on Mac/Linux/BSD, which is a minor advantage, but not huge. floats will still be packed in a GP register, though.
Result type
From an API design perspective, the nice thing about a Result object is that the caller cannot ignore the possibility of an error. They may or may not remember to check for NaN or catch exceptions but with Result, they always have to unpack the contained value and in doing so, decide on their desired error handling.
From a performance perspective, the main point when writing a Result type is that you don't want to make it more expensive to access the actual return value unless you don't care about runtime and code size. This means making sure the return value can be passed in registers instead of the stack.
On Windows this is very hard to achieve because the Windows calling convention only uses a single register for return objects and I don't think they pack two 32 bit values into one 64 bit register. At this point your only options are a) accept the cost of stack return values b) try to pack error code and result value in one scalar like you did with NaN payloads or other tricks like negative integers c) not use this approach.
On all other major x86-64 platforms, you have two registers to work with. This is far more feasible unless you regularly return 16 byte payloads like std::complex<double>.
However, for this to work, the Result must not have a non-trivial destructor or copy/move constructor. For all intents and purposes, this means you cannot have dynamic error messages in the Error type. There are ways around this, if you absolutely need: You enforce that every access to the actual result also checks the error and deallocates, either reporting or ignoring it in the process. Use [[nodiscard]] on the return values to ensure the return value is checked at all. This works, for example:
struct Error
{
std::string* message;
private:
[[noreturn]] static void raise_and_delete_msg(std::string*);
public:
/*
* Note: clang needs always_inline to generate efficient
* code here. GCC is fine
*/
[[noreturn, gnu::always_inline]] void raise() const
{ raise_and_delete_msg(message); }
void discard() const noexcept
{ delete message; }
operator bool() const noexcept
{ return message != nullptr; }
void check() const
{
if(message)
raise();
}
};
template<class T>
class Result
{
T result;
Error err;
public:
constexpr Result()
: result(),
err()
{}
explicit Result(T result)
: result(std::move(result)),
err()
{}
/** Takes ownerhip of message. Will delete */
explicit Result(std::unique_ptr<std::string>&& message)
: err(Error{message.release()})
{}
Result(std::unique_ptr<std::string>&& message, T invalid)
: result(std::move(invalid)),
err(Error{message.release()})
{}
T unchecked() noexcept
{
err.discard();
return std::move(result);
}
T checked()
{
err.check();
return std::move(result);
}
bool unpack(T& out) noexcept
{
if(err) {
err.discard();
return false;
}
out = std::move(result);
return true;
}
};
[[nodiscard]] Result<double> foo();
double bar()
{
return foo().checked() + 1.;
}
However, at this point you quickly reach the point where you exceed the 8 bytes you can reasonably use for sizeof(Error) before you go back to stack return values so I'm not sure this is worth it. For example if you want and error code plus message, you need to dynamically allocate both or do other fancy tricks. Plus, [[nodiscard]] is only a warning, so you can still easily get memory leaks.
Conclusion
If I have to make suggestions:
Use exceptions if a) they are in line with the coding style and API you normally use plus b) the expectations that both you and your API users have on these functions and c) failure should be rare, costly, and loud
Use Error output arguments if you primarily target Windows or if you want complex error reporting with dynamic messages or similar.
Use Result for simple error codes on Linux/Mac or if you want your API users to always make a conscious decision to check or ignore an error. In that case, you may also accept the additional runtime cost associated with complex Error objects or any such object on Windows.
Related
I am currently building an embedded system and use a modern C++ compiler.
While I could technically fit exception handling in the given resources (ARM7, more than 10M RAM), I don’t think exceptions are the right tool for something like this and using exceptions requires RTTI, which in turn results in code bloat.
To stay C++-ish anyway I want to use std::error_code (or similar with more data) because I do like the concept.
However, there does not seem to be any consenus on how to actually use them. I have seen at least four different ways of passing them between function calls, two of them with multiple semantics.
Passing by pointer as an argument
void somefunction(Args..., std::error_code* error);
This is the way I have not seen that often and the one I dislike the most. It leaves the return type fully available and (often, but not always) passing nullptr resulted in normal throwing behaviour.
Passing by reference as an argument
void somefunction(Args..., std::error_code& error);
This is the one I prefer. It leaves returnvalue fully available and makes clear that the error_code is not optional.
Returning it by value
std::error_code somefunction(Ret& out <= if used, Args...);
I have seen this one quite often but don’t really like it that much, as it uses up your return value and I generally don’t like “out parameters” unless there’s no way around them.
Returning a std::variant<Ret, std::error_code>
std::variant<Ret, std::error_code> somefunction(Args...);
This one allows for a return value, but makes accessing both value and error harder. Also, it makes code calling the function more verbose.
Semantics
I have seen both way 1 and 2 with different semantics, if the error_code is passed.
Clear at start and set on error
Only set on error
Return right at start if the error_code is “set”
The last way is pretty good if you want to reduce error checking in the calling code. As you can just pass one error_code to multiple functions without checking in between and everything after the first error will not execute, similar to how exceptions would do it.
I personally do prefer way 2 with checking and returning, however I might be biased.
Is there some recommended / generally accepted way to do it?
Ok, this is no complete answer and actually not perfectly on topic because I am not aware of a standard way to do this. But I once saw a nifty little trick to make error codes harder to misuse. Consider the following code:
struct MyEC {
MyEC() {}
MyEC(MyEC && other) : parent(&other) {
// Maybe log and or abort if other is not checked
other.checked = false;
}
// Delete other constructors and assignment operators
~MyEC() {
if(!checked && parent == nullptr) {
// log and or abort
}
}
[[nodiscard]] std::error_code check() {
checked = true;
return ec;
}
void set(std::error_code err) {
if(parent == nullptr) ec = err;
else parent->set(err);
}
private:
MyEC* parent = nullptr;
checked = true;
std::error_code ec {};
};
int foo(MyEC&& err) {
err.set(/* some error */);
return 5;
}
int foo1(MyEC&&) {
return 4;
}
void bar() {
MyEC err;
foo(std::move(err));
// err has the error code and if its not checked, we will know
foo1(std::move(err));
// even though no error occurs, we will abort if err is not checked.
}
It will even then abort, when the error code is not set but also not checked, which is pretty nice. It has a lot of uses after move, which is a bit weird, but this is no problem here.
I often use -1 as the invalid value type when returning from a function, where the input yields incorrect output. For instance, writing an indexing function where the index is out of bounds, instead of throwing an exception, -1 can be returned. But when writing a function that has negative values as possible return types, this technique does not work. What is the correct way to return an invalid type value in such instances?
The technique I use mostly is to set the return type to be of type *int, and return a Pointer to NULL. But, that requires all return values to be of a pointer type, which seems like an extra overhead to the function. Is there an accepted standard for returning values in such cases?
In newer C++, I'd suggest using std::optional<>; if you don't yet have it, boost::optional<>.
One option would be to let your function take a bool& as an output parameter used to indicate if the returned value is valid.
int myFunc(bool& valid); // sets 'valid' to true if result is usable, false otherwise
Users can then do
bool valid = false;
Int result = myFunc(valid);
if (!valid) {
// Handle error
}
// Use result
Not the most pretty solution, but it does the job.
Apart from the answer I provided above, there's a very clean, continuation-passing solution (given you're non-virtual):
template<typename Success, typename Failed>
void parse( const std::string& str, Success s, Failed f )
{
auto a = start_parse(str);
if( a.problem() )
return f(); // you _might_ have an error code here
s( finish_parse(str, a) );
}
Then you might customize by:
Success:
[&i] (int i_) { i = i_; }
out(i), where out(int& output_) returns the above lambda for output_
actual code doing something useful
function to continue with
Failed:
[&i]{ i = 0; }, `[&i]{ i = nullopt; }, or any other default value
[] { throw MyFavouriteException(); }
retry logic
std::terminate()
[]{} if you don't care (or if you're 100% sure it'll succeed)
It might look a little verbose, but IMHO:
it's trivial to read
any other schematics can be mimicked, even if there's no default c'tor
easy to change as well
'you don't pay for what you don't use', can surely be optimized away
every schematic is visible and apparent from code:
for default value, caller sets it, not callee or global
std::optional<> and default value are handled the same
for exception, caller knows better what to throw
for no action, you don't have to lookup the implementation to know this
for std::terminate(), well, you know what to expect
if you 'speak' CPS, you might actually continue and save an if / catch / etc.
The only issue I see is constructor initializer lists. Any thoughts on this?
Let us assume I always need the direkt return type of the function to be of a errorcode (success of calculation or failure) , then I will return some arguments as parameters. Is it better to define them as reference (and create them before empty) or better to return pointer?
Edit: I should be more precise: The errorcode is obligatory because I have to stick to the coding guidline given.
Possibility A:
ErrorCode func( some_parameters ... , ReturnType & result)
...
ReturnType result; // empty constructor, probably not good practice
func( some_parameters ..., result)
Possibility B:
ErrorCode func( some_parameters ... , ReturnType * result){
...
result = new ReturnType(...)
...
}
...
ReturnType * result; // void pointer
func( some_parameters ..., result)
...
delete result; // this is needed if I do not use a smart pointer
Even better: Maybe you have a more appropriate solution?
Edit: Please indicate which standard you are using, since unfortunatelly (guidelines) I have to stick to C++98.
I would do the following (and in general do)
1.) throw an exception instead of returning error codes
if this is not possible (for any reason)
2.) return the pointer directly (either raw or std::unique_ptr) and return nullptr for failure
if return type has to be bool or not all objects returned are (pointers / heap allocated)
3.) return your error code (bool or enum class) and accept a reference parameter for all objects that are to be initialized (must have objects so to speak) and pointers to objects that may be optionally created / initialized
if the object cannot be created in advance to the call (e.g. because it is not default constructible)
4.) pass a reference to a pointer (raw or std::unique_ptr) or a pointer to a pointer, which will then be filled by the function
std::optional (or similar) may be an option if you only have a true/false return code.
I don't like returning std::pair or std::tuple because it can make your code look quite annoying if you have to start using .first/.second or std::get<>() to access your different return types. Using std::tie() can reduce this a little bit, but it is not (yet) very comfortable to use and prevents the use of const.
Examples:
std::unique_ptr<MyClass> func1() { /* ... */ throw MyException("..."); }
std::unique_ptr<MyClass> func2() { /* ... */ }
ErrorCode func3(MyClass& obj, std::string* info = nullptr) { /* ... */ }
ErrorCode func4(std::unique_ptr<MyClass>& obj) { /* ... */ }
int main()
{
try
{
auto myObj1 = func1();
// use ...
}
catch(const MyException& e)
{
// handle error ...
}
if(auto myObj2 = func2())
{
// use ...
}
MyClass myObj3;
std::string info;
ErrorCode error = func3(myObj3, &info);
if(error == ErrorCode::NoError)
{
// use ...
}
std::unique_ptr<MyClass> myObj4;
ErrorCode error = func4(myObj4);
if(error == ErrorCode::NoError)
{
// use ...
}
}
Edit: And in general it is advisable to keep your API consistent, so if you already have a medium or large codebase, which makes use of one or the other strategy you should stick to that (if you do not have good reasons not to).
This is a typical example for std::optional. Sadly it isn't available yet, so you want to use boost::optional.
This is assuming that the result is always either "success with result" or "fail without result". If your result code is more complicated you can return
std::pair<ResultCode, std::optional<ReturnType>>.
It would be good style to to use the return value for all return information. For example:
std::tuple<bool, ReturnType> func(input_args....)
Alternatively, the return type could be std::optional (or its precursor) if the status code is boolean, with an empty optional indicating that the function failed.
However, if the calculation is supposed to normally succeed, and only fail in rare circumstances, it would be better style to just return ReturnType, and throw an exception to indicate failure.
Code is much easier to read when it doesn't have error-checking on every single return value; but to be robust code those errors do need to be checked somewhere or other. Exceptions let you handle a range of exceptional conditions in a single place.
Don't know if it's applicable in your situation but if you have only two state return type then maybe just return pointer from your function and then test if it is nullptr?
I have inherited a (large) piece of code which has an error tracking mechanism where they pass in a boolean variable to all the methods they call and on errors at various stages of execution the method is stopped and returns, sometimes a default value.
Something like (BEFORE):
#include <iostream.h>
int fun1(int par1, bool& psuccess)
{
if(par1 == 42) return 43;
psuccess = false;
return -1;
}
int funtoo(int a, bool& psuccess)
{
int t = fun1(a, psuccess);
if(!psuccess)
{
return -1;
}
return 42;
}
void funthree(int b, bool& psuccess)
{
int h = funtoo(b, psuccess);
if(!psuccess)
{
return;
}
cout << "Yuppi" << b;
}
int main()
{
bool success = true;
funthree(43, success);
if(!success)
{
cout<< "Life, universe and everything have no meaning";
}
}
Please note, that this is a mixture of C and C++ code, exactly the way the project is in.
Now, comes a piece of C magic: "someone" somewhere defined a macro:
#define SUCCES_OR_RETURN if(!psuccess) return
And the program above becomes (AFTER):
#include<iostream.h>
int fun1(int par1, bool& psuccess)
{
if(par1 == 42) return 43;
psuccess = false;
return -1;
}
int funtoo(int a, bool& psuccess)
{
int t = fun1(a, psuccess);
SUCCES_OR_RETURN -1;
return 42;
}
void funthree(int b, bool& psuccess)
{
int h = funtoo(b, psuccess);
SUCCES_OR_RETURN ;
std::cout << "Yuppi" << b;
}
int main()
{
bool success = true;
funthree(43, success);
if(!success)
{
cout<< "Life, universe and everything have no meaning";
}
}
The question: I am wondering if there is a nicer way to handle this kind of error tracking or I have to live with this. I personally don't like the abuse of the C macro SUCCES_OR_RETURN ie. that once it is called with a parameter, and in other cases it is called without, feels like a real return statement, but I did not find any better solutions to this ancient design.
Please note that due to platform restrictions we have several restrictions, but regardless of it I am willing to hear opinions about these two:
throwing exceptions. The code is a mixture of C and C++ functions calling each other and the compiler sort of does not support throw (accepts in the syntax but does nothing with it, just a warning). This solution is sort of the standard way of solving this problem in a C++ environment.
C++11 features, this goes to a tiny embedded platform with an obscure and ancient "almost" C++ compiler which wasn't made to support the latest C++ features. However for future reference I am curios if there is anything C++11 offers.
template magic. The compiler has problems understanding complex templated issues, but again I am willing to see any solutions that you can come up with.
Edit
Also, as #BlueMoon suggested in the commend, creating a global variable is not working since at a very beginning of the function chain calling the success variable is a member variable of a class, and there are several objects of this class created, each of them needs to report its success status :)
There's a great breakdown of hybrid C and C++ error handling strategies here:
http://blog.sduto.it/2014/05/a-c-error-handling-style-that-plays.html
To quote the linked article, your options largely boil down to:
Return an error code from functions that can fail.
Provide a function like Windows's GetLastError() or OpenGL's glGetError() to retrieve the most recently occurring error code.
Provide a global (well, hopefully, thread-local) variable containing the most recent error, like POSIX's errno.
Provide a function to return more information about an error, possibly in conjunction with one of the above approaches, like POSIX's strerror function.
Allow the client to register a callback when an error occurs, like GLFW's glfwSetErrorCallback.
Use an OS-specific mechanism like structured exception handling.
Write errors out to a log file, stderr, or somewhere else.
Just assert() or somehow else terminate the program when an error occurs.
It seems like the author of the code you have inherited picked a rather strange way, passing a pointer to a boolean [sic] for the function to work with seems rather unusual.
The article has some great examples, personally I like this style:
libfoo_widget_container_t container = NULL;
libfoo_error_details_t error = NULL;
if (libfoo_create_widgets(12, &container, &error) != libfoo_success) {
printf("Error creating widgets: %s\n", libfoo_error_details_c_str(error));
libfoo_error_details_free(error);
abort(); // goodbye, cruel world!
}
Here you get a bit of everything, passed in pointer to error type, a comparison against a success constant (rather than 0|1, a painful dichotomy between C and the rest of the world!).
I don't think it would be too much of a push to say that your macro could rather better be implemented with a goto, in any case, if a function is calling SUCCES_OR_RETURN more than once, it might be a clue that the function is doing too much. Complex cleanup, or return might be a code smell, you can read more here http://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c/
I have seen this style of error handling before. I call it error-oblivious manual pseudo-exceptions.
The code flow is mostly error-oblivious: you can call 3 functions in a row with the same error flag, then look at the error flag to see if any errors have occurred.
The error flag acts as a pseudo-exception, where once set we start "skipping" over normal code flow, but this is done manually instead of automatically.
If you do something and do not care if an error occurs, you can just drop the error produced and proceed on.
The ICU library handles errors in a similar way.
A more C++1y way to do this while minimizing structural differences would be to modify code to return an expected object.
An expected<T, Err> is expected to be a T, and if something went wrong it is instead an Err type. This can be implemented as a hybrid of boost::variant and C++1y's std::optional. If you go and overload most arithmetic operations on expected< T, Err > + U to return expected< decltype( std::declval<T&>() + std::declval<U>(), Err > and did some careful auto, you could allow at least arithmetic expressions to keep their structure. You'd then check for the error after the fact.
On the other hand, if the error return values are predictable based on their type, you could create a type that when cast to a given type produced an error value. Modify functions returning void to return an error object of some kind while you are at it. And now every function can
if (berror) return error_flag_value{};
which at least gets rid of that strange ; or -1; issue.
If you want to go full C++, the answer would be changing the "invalid return values" for exceptions...
#include <iostream>
#include <exception>
using std::exception;
struct error : exception { const char* what() const throw() override { return "unsuccessful"; } };
int fun1(int par1) {
if( par1 == 42 ) return 43;
throw error();
}
int funtoo(int a) {
fun1(a);
return 42;
}
void funthree(int b) {
funtoo(b);
std::cout << "Yuppi " << b << "\n";
}
int main() {
try {
funthree(42);
} catch(exception& e) {
std::cout << "Life has no meaning, because " << e.what() << "\n";
}
}
This prints Yuppi 42 (if you change the call funthree(42) for funthree(43) it prints Life has no meaning, because unsuccessful...)
(live at coliru)
I have a setup that looks like this.
class Checker
{ // member data
Results m_results; // see below
public:
bool Check();
private:
bool Check1();
bool Check2();
// .. so on
};
Checker is a class that performs lengthy check computations for engineering analysis. Each type of check has a resultant double that the checker stores. (see below)
bool Checker::Check()
{ // initilisations etc.
Check1();
Check2();
// ... so on
}
A typical Check function would look like this:
bool Checker::Check1()
{ double result;
// lots of code
m_results.SetCheck1Result(result);
}
And the results class looks something like this:
class Results
{ double m_check1Result;
double m_check2Result;
// ...
public:
void SetCheck1Result(double d);
double GetOverallResult()
{ return max(m_check1Result, m_check2Result, ...); }
};
Note: all code is oversimplified.
The Checker and Result classes were initially written to perform all checks and return an overall double result. There is now a new requirement where I only need to know if any of the results exceeds 1. If it does, subsequent checks need not be carried out(it's an optimisation). To achieve this, I could either:
Modify every CheckN function to keep check for result and return. The parent Check function would keep checking m_results. OR
In the Results::SetCheckNResults(), throw an exception if the value exceeds 1 and catch it at the end of Checker::Check().
The first is tedious, error prone and sub-optimal because every CheckN function further branches out into sub-checks etc.
The second is non-intrusive and quick. One disadvantage is I can think of is that the Checker code may not necessarily be exception-safe(although there is no other exception being thrown anywhere else). Is there anything else that's obvious that I'm overlooking? What about the cost of throwing exceptions and stack unwinding?
Is there a better 3rd option?
I don't think this is a good idea. Exceptions should be limited to, well, exceptional situations. Yours is a question of normal control flow.
It seems you could very well move all the redundant code dealing with the result out of the checks and into the calling function. The resulting code would be cleaner and probably much easier to understand than non-exceptional exceptions.
Change your CheckX() functions to return the double they produce and leave dealing with the result to the caller. The caller can more easily do this in a way that doesn't involve redundancy.
If you want to be really fancy, put those functions into an array of function pointers and iterate over that. Then the code for dealing with the results would all be in a loop. Something like:
bool Checker::Check()
{
for( std::size_t id=0; idx<sizeof(check_tbl)/sizeof(check_tbl[0]); ++idx ) {
double result = check_tbl[idx]();
if( result > 1 )
return false; // or whichever way your logic is (an enum might be better)
}
return true;
}
Edit: I had overlooked that you need to call any of N SetCheckResultX() functions, too, which would be impossible to incorporate into my sample code. So either you can shoehorn this into an array, too, (change them to SetCheckResult(std::size_t idx, double result)) or you would have to have two function pointers in each table entry:
struct check_tbl_entry {
check_fnc_t checker;
set_result_fnc_t setter;
};
check_tbl_entry check_tbl[] = { { &Checker::Check1, &Checker::SetCheck1Result }
, { &Checker::Check2, &Checker::SetCheck2Result }
// ...
};
bool Checker::Check()
{
for( std::size_t id=0; idx<sizeof(check_tbl)/sizeof(check_tbl[0]); ++idx ) {
double result = check_tbl[idx].checker();
check_tbl[idx].setter(result);
if( result > 1 )
return false; // or whichever way your logic is (an enum might be better)
}
return true;
}
(And, no, I'm not going to attempt to write down the correct syntax for a member function pointer's type. I've always had to look this up and still never ot this right the first time... But I know it's doable.)
Exceptions are meant for cases that shouldn't happen during normal operation. They're hardly non-intrusive; their very nature involves unwinding the call stack, calling destructors all over the place, yanking the control to a whole other section of code, etc. That stuff can be expensive, depending on how much of it you end up doing.
Even if it were free, though, using exceptions as a normal flow control mechanism is a bad idea for one other, very big reason: exceptions aren't meant to be used that way, so people don't use them that way, so they'll be looking at your code and scratching their heads trying to figure out why you're throwing what looks to them like an error. Head-scratching usually means you're doing something more "clever" than you should be.