user9150170
user9150170

Reputation:

How to return an error from a function that returns signed integers

I have some psuedocode for a function to display a value and get a value

int getValue(){
    int value;
    // open file
    // read line into "value"
    if(error occurs){ 
        // if file doesn't open or line was not an integer
        /* Normally I would return something such as -1
           but -1 in this case would be a valid value*/
        value = ?
    }
    return value;
}

void displayValue(){
    int value = getValue();
    if(value is valid)
         display(value);
}

As described in the code above, I would like to return that there was an error and let displayValue know that there was an error. But i want to accept negative,positive, and 0 from getValue.

Is there a better way to go about this? Does anyone have any advice?

Upvotes: 1

Views: 1397

Answers (4)

eerorika
eerorika

Reputation: 238331

There are several error handling approaches in C++:

The traditional way popular in C API's (also used by std::string algorithms) is to reserve at least one value as "invalid", which when returned would signal that there was an error, or that the value represents "no result". In case of error, the C API's would use the global errno to inform what error happened.

One of the features C++ introduced over the C language is exceptions. You can simply throw when error occurs. This is most appropriate for unexpected errors or pre/post-condition violations, rather than "no result exists" type situations.

Yet another way is to return both the value, and information about whether the result is valid. Old fashioned approach might be to return a pair of integer and boolean (or a named class that achieves the same). Alternatively, either value or error state can written into object passed through indirection. std::optional has been introduced into the standard library just for this kind of situation an is a great way of representing lack of result.

Latter approach can be further extended to not only return a boolean, but actual information about the error in similar way to the way exceptions do. The error information can also be wrapped with the value in a "variant" type so that they can share the space, as only one of them can exist at any time. This approach is similar to Maybe type in Haskell. There is a proposal to introduce a template for this purpose into the standard library.

Each of these approaches have their benefits and drawbacks. Choose one that is appropriate for your use case.

Upvotes: 4

ShadowRanger
ShadowRanger

Reputation: 155363

Throw an exception. One of the advantages of C++ over C is that, when you have an error, you don't have to smuggle error codes out of the function, you can just throw an exception. If it's a truly exceptional case, most of the time the caller won't have anything useful to do with it anyway, so forcing them to check for it manually, then pass the error up the call chain is pointless. If they do know what to do with it, they can catch it.

This solution is also more composable. Imagine a scenario where A returns int, B calls A and returns a std::string based on it, and C calls B and returns class Foo based on that. If A has an exceptional condition that requires it to return an error, you can either:

  1. Come up with some way to smuggle the error out of A as an int (or std::optional<int> or std::pair<bool, int> or whatever), then check for and convert that smuggled error to a different smuggled error for B, then check for and convert that to yet another smuggled error for C, then the caller of C still needs to check for that smuggled error and all three layers have to pay the price of the checks every time, even when all three layers succeeded, or...
  2. You throw an exception in A, neither B nor C have anything useful to do with it (so they don't write any additional code at all), and the caller of C can choose to catch the exception and produce a friendlier error message if they so choose.

On modern architectures, the cost in the success case for #2 should be pretty negligible; the failure case might be more costly than the "check at every level case", but for something like "file doesn't exist" or "file contains corrupt data", it hardly matters if performance suffers, since you're probably about to exit the program (so speed doesn't count) or pop a dialog the user needs to respond to (the user is slower than the computer by many orders of magnitude).

Upvotes: 5

Bathsheba
Bathsheba

Reputation: 234695

Other than throwing an exception, returning a std::optional, or a std::pair, there is a precedent here: std::string::npos is normally set to a particularly large std::string::size_type value, normally -1 (wrapped around of course) and is used by some std::string functions to indicate a failure.

If you're willing to give up one legitimate return value then you could do something similar in your case. In reality though, typical (perhaps all) strings will be significantly smaller than npos; if that's not the case for you then perhaps one of the alternatives already mentioned would be better.

Upvotes: 2

Jeffrey
Jeffrey

Reputation: 11400

One option is to throw an exception when an error occurs. It's highly dependent on the rest of your project. Are Exceptions used all around ? Personally, I prefer more conventional old-school approaches. Mostly because people will start throwing exception everywhere, where it's not really exceptional and then it makes debugging much harder as the debugger keeps stopping for non-exceptional situations.

Another option is to return a pair std::pair<bool, int>. Some people love it, some people hate it.

My preference would be bool attemptGetValue(int& outValue). You return false if there's an error, in which case you don't touch outValue. Your return true otherwise and modify outValue

You can also use std::optional, but old timers might not be familiar wiht it.

Upvotes: 3

Related Questions