-4

Let's say I want to call four functions consecutively, which operate on some object of mine. If any of them fails, I want to return FAILURE without calling the others, and I want to return SUCCESS iff all of them completed successfully.

Normally, I would do something like this:

if(function_zero(&myMutableObject) == SUCCESS)
{
    return FAILURE;
}
if(function_one(&myMutableObject) == SUCCESS)
{
    return FAILURE;
}
if(function_two(&myMutableObject) == SUCCESS)
{
    return FAILURE;
}
if(function_three(&myMutableObject) == SUCCESS)
{
    return FAILURE;
}

return SUCCESS;

Or, if I needed to do some cleanup:

if(function_zero(&myMutableObject) == SUCCESS)
{
    status = FAILURE;
    goto cleanup;
}
if(function_one(&myMutableObject) == SUCCESS)
{
    status = FAILURE;
    goto cleanup;
}
if(function_two(&myMutableObject) == SUCCESS)
{
    status = FAILURE;
    goto cleanup;
}
if(function_three(&myMutableObject) == SUCCESS)
{
    status = FAILURE;
    goto cleanup;
}

cleanup:
// necessary cleanup here
return status;

However, the project I am working on has some restrictions:

  • No goto, ever
  • No early return (one return per function)
  • Line length limit
  • (EDIT) No exceptions.
  • (EDIT) No templates.

This leads me to something like this:

if(function_zero(&myMutableObject) == SUCCESS)
{
    if(function_one(&myMutableObject) == SUCCESS)
    {
        if(function_two(&myMutableObject) == SUCCESS)
        {
            status = function_three(&myMutableObject);
        }
        else
        {
            status = FAILURE;
        }
    }
    else
    {
        status = FAILURE;
    }
}
else
{
    status = FAILURE;
}

return status;

Unfortunately, this pushes me up against the line length limit often.

My question to you is: Is there a simpler way of writing this?

Notes & Restrictions:

  • I must implement this logic within the code block implied here. I cannot create new functions, refactor or change the overall architecture.
  • (EDIT) In reality the functions have very different signatures.
Adam S
  • 8,945
  • 17
  • 67
  • 103

7 Answers7

7

Use exceptions and RAII. These are literally the problems they were invented to solve. Exceptions though are more of a system-wide feature, rather than something you can apply locally.

For the cleanup block, RAII is exactly the feature you need.

For the success/failure, we can use lambdas and variadics to chain them together implicitly.

Now we can simply write them as lambdas in the list.

status f() {
    struct nested {
        static template<typename F> status_t Check(F f) {
            return f();
        }
        static template<typename F, typename... Chain> status_t Check(F f, Chain... chain) {
            auto status = f();
            return status != failure ? Check(chain...) : status;
        }
    };
    return nested::Check( 
        [] { return function_zero(&myMutableObject); },
        [] { return function_one(&myMutableObject); },
        [] { return function_two(&myMutableObject); },
        [] { return function_three(&myMutableObject); },
    );
}

This becomes slightly more problematic if you need to capture the return value, but since it seems to be always an error code with out parameter, it should be fine if you simply declare the receiving variable in f(), then all the future lambdas can refer to it. It also does not require that every function has the same signature, or allocating various data structures.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • `f()` only has one `return` point. – Puppy Jul 18 '14 at 20:15
  • I forgot to say, no exceptions either. – Adam S Jul 18 '14 at 20:16
  • Yeah, Check has two return points, but I fixed that trivially. – Puppy Jul 18 '14 at 20:16
  • May I suggest `if (status != failure) status = Check(chain...); return status;` rather than `?`? Because I suspect that side effects in `?` might be banned in a coding standard that bans as much as the OP's does! – Yakk - Adam Nevraumont Jul 18 '14 at 20:17
  • Eh, I feel that it really doesn't matter exactly how you phrase the non-base-case of Check. It can be phrased in many ways that meet the requirements. Hell, you could even use a polylambda in C++14, but that's a bit more risky. – Puppy Jul 18 '14 at 20:17
  • Sorry, I forgot to add, no templates either and the functions have different signatures. – Adam S Jul 18 '14 at 20:19
  • 8
    The functions having different signatures is fine, but seriously. You forgot to add that you can't use practically every language feature?! – Puppy Jul 18 '14 at 20:20
  • By one return, the intent is that the "return" statement appears exactly once *anywhere* in the function body. – Adam S Jul 18 '14 at 20:21
  • 3
    Find whoever set those restrictions and smack them repeatedly because they're totally moronic. Then find a new job. Then post a question that actually details ALL of your requirements, instead of just a few of them, maybe. (Also, f() has only one return statement in the body.) – Puppy Jul 18 '14 at 20:22
  • Yeah, sorry about all the omissions. It is my first time writing C++ coming from a C background, so I wasn't familiar enough with the features to preempt the creative responses here. – Adam S Jul 18 '14 at 20:22
  • 2
    @Jefffrey, I understand it can be frustrating, but having complete knowledge of a language is not a prerequisite for asking a question. Asking questions *is* a method of learning. – Adam S Jul 18 '14 at 20:33
  • @AdamS: Yes, but when you get answers that involve language features you don't know, then it's a sign that you should go look at them, because they can solve your problem. Not a sign that you should ignore problem solutions because they require learning. – Puppy Jul 18 '14 at 20:39
  • 9
    @AdamS I understand you can be frustrating, but having incomplete knowledge of a language is a bizarre reason for dismissing the right answers. Accepting answers _is_ a method of learning. (Dismissing them is a waste of time and karma.) – sehe Jul 18 '14 at 21:00
  • There are techniques for transitioning exception unsafe code to exceptions. exceptionsafecode.com covers some examples. – bames53 Jul 18 '14 at 21:13
5

Only call the tests if status is currently set to SUCCESS using an && operator. If it is set to FAILURE the && will fail immediately and the subsequent tests will not be executed.

status = SUCCESS

if (status == SUCCESS && function_zero(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
if (status == SUCCESS && function_one(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
if (status == SUCCESS && function_two(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
if (status == SUCCESS && function_three(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}

return status;

As @Mooing Duck suggested, you could simply do it all in an else if chain:

status = SUCCESS

if (function_zero(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
else if (function_one(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
else if (function_two(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}
else if (function_three(&myMutableObject) == FAILURE)
{
    status = FAILURE;
}

return status;
kbirk
  • 3,906
  • 7
  • 48
  • 72
  • 1
    I see this variant sometimes too: http://coliru.stacked-crooked.com/a/0d055ce525f826de, which makes it blatantly obvious to the compiler that after one fails, it can jump straight to the end. – Mooing Duck Jul 18 '14 at 20:19
  • you could add cleanup at the end: `if (status == FAILURE) { cleanup code here }`, what to do with exception remain as a problem. – NetVipeC Jul 18 '14 at 20:20
  • Since it turns out the OP can't use C++, I'm not gonna leave my downvote on. But for the record, the question as originally posted, this was a TERRIBLE answer. – Puppy Jul 18 '14 at 20:25
3

A pattern that I used several times when dealing with a long chain of C calls (typically WinAPIs) goes like this:

bool ret =
    function_zero(&myMutableObject) == SUCCESS
    &&
    function_one(&myMutableObject) == SUCCESS
    &&
    function_two(&myMutableObject) == SUCCESS
    &&
    function_three(&myMutableObject) == SUCCESS;
if(!ret)
{
    // cleanup
}
return ret?SUCCESS:FAILURE;

You may even leave the && at the end of each line, so that it looks more like a "normal" sequence of calls (although personally I like them better this way, it's clearer what's going on).

The && operator guarantees execution in the right order and only if the previous calls succeeded, and introduces the necessary sequence points (or however they are called in C++11) between the calls, so the order of evaluation of parameters between the various calls is well defined. Also, it has low enough priority not to require additional parentheses.

If you are not afraid of using COM-style macros, you can also encapsulate the == SUCCESS check in a macro, like

// in some header, maybe with a less abused name
#define OK(x) ((x) == SUCCESS)

bool ret =
    OK(function_zero(&myMutableObject))
    &&
    OK(function_one(&myMutableObject))
    &&
    OK(function_two(&myMutableObject))
    &&
    OK(function_three(&myMutableObject));
// ...

Even better, if SUCCESS != 0 and FAILURE == 0 you can drop the OK() and == SUCCESS altogether and just use && to link the calls.

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
2

You say no exceptions, but I think you ought to know what you're giving up with that.

If you use RAII based cleanup and report errors as exceptions rather than codes, then you get code that looks like this:

function_zero(&myMutableObject);
function_one(&myMutableObject);
function_two(&myMutableObject);
function_three(&myMutableObject);

Here's a site that explains correct C++ exception handling and the benefits:

http://exceptionsafecode.com/

Some of the benefits are:

  • easier to read
    • easier to understand and maintain
    • wrong code looks wrong
  • easier to write
  • improved performance on the success path
    • 'zero-cost' exceptions
    • compiler understands exceptions as a language feature, knows which path is the success path and which is the failure path
    • code size increase for exception tables is offset by elimination of error checking code

Furthermore, Herb Sutter has this to say on the subject of 'single exit' (the common name for your "No early return" rule):

In general, note that SE/SE is an obsolete idea and has always been wrong. “Single entry,” or the idea that functions should always be entered in one place (at their start) and not with goto jumps from the caller’s code directly to random places inside the function body, was and is an immensely valuable advance in computer science. It’s what made libraries possible, because it meant you could package up a function and reuse it and the function would always know its starting state, where it begins, regardless of the calling code. “Single exit,” on the other hand, got unfairly popular on the basis of optimization (‘if there’s a single return the compiler can perform return value optimization better’—see counterexample above) and symmetry (‘if single entry is good, single exit must be good too’) but that is wrong because the reasons don’t hold in reverse—allowing a caller to jump in is bad because it’s not under the function’s control, but allowing the function itself to return early when it knows it’s done is perfectly fine and fully under the function’s control.

http://herbsutter.com/category/c/gotw/page/4/

You should try to get the rules updated, even if that's only possible for new projects.

bames53
  • 86,085
  • 15
  • 179
  • 244
1

You could store the function pointers in a containers of std::function (as long as they have the same signature as in the example):

std::vector<std::function<error_code (myobject&)> functions { func1, func2, func3, func4 };

error_code status = SUCCESS;
for (const auto& f : functions) {
    if (f(myobject) == ERROR) {
        clean_up();
        status = ERROR;
        break;
    }
}

return status;
NetVipeC
  • 4,402
  • 1
  • 17
  • 19
  • Sorry, I forgot to mention the function have different signatures. I over-simplified for the sake of explanation. Sorry about that. – Adam S Jul 18 '14 at 20:17
1

This could work:

bool success = true;
success = success && function_zero(&myMutableObject) != FAILURE;
success = success && function_one(&myMutableObject) != FAILURE;
success = success && function_two(&myMutableObject) != FAILURE;
success = success && function_three(&myMutableObject) != FAILURE;

return success ? SUCCESS : FAILURE;

return can be replaced to:

int status = SUCCESS;
if( !success ) status = FAILURE;
return status;

if in your company conditional operator is prohibited as well.

Slava
  • 43,454
  • 1
  • 47
  • 90
0

You might write a simple local functor:

bool fn0(int&) { return false; }
bool fn1(int&) { return false; }
bool fn2(int&) { return false; }

int main() {
    struct Test {
        const bool result;

        typedef bool (*function)(int&);
        Test(function f, int& value)
        :   result(f(value))
        {};

        operator bool () const { return result; }
    };

    int value;
    return Test(fn0, value)
        && Test(fn1, value)
        && Test(fn2, value);

}