Home > Enterprise >  Write a macro to check function return code
Write a macro to check function return code

Time:06-10

Quite common that we have to call set of functions that have similar behavior - for example return negative value and set errno variable in case of failure. Instead of repeatedly write check code, sometimes it is useful to wrap it into macro:

#define CHECKED_CALL( func, ... ) \
    do {\
        auto ret = func( __VA_ARGS__ );\
        if( ret < 0 ) {\
            std::cerr << #func "() failed with ret " << ret << " errno " << errno << std::endl;\
            throw std::runtime_error( #func "()" failed" );\
         }\
    while(false)

then use is simple:

 CHECKED_CALL( func1, 1, 2, 3 );
 CHECKED_CALL( func2, "foobar" );

etc. Now let's say I need to get result of the call in case it did not fail. Something like:

 auto r = CHECKED_CALL( func3, 1.5 );

Obviously this would not work as written and of course I can write another macro, but question is, is it possible to write macro that would work in both cases? Or maybe not macro but not very complicated code, that will take me 10 times more time to implement than to write yet another macro for value return. Especially not to generate warning of unused code etc.

CodePudding user response:

Almost everything in your CHECKED_CALL macro can be turned into a function template, which has the advantage of passing through the C compiler's type system instead of the preprocessor's brute textual substitution.

So, for instance, we can write

template<class Callable, class... Args>
void checked_call(Callable t_callable, std::string t_functionName, Args&&... t_args)
{
    auto ret = t_callable(std::forward<Args>(t_args)...);
    if( ret < 0 ) 
    {
    std::cerr << t_functionName << "() failed with ret " << ret << " errno " << errno << std::endl;
    throw std::runtime_error( t_functionName   "() failed" );
    }
}

One downside here is that checked_call requires us to explicitly pass a std::string representing the name of a function.

Example calling code:

int failingFunction()
{
    return -1;
}

int main(){
    checked_call(failingFunction, "failingFunction");
}

We can introduce a macro that automatically pairs a function with a string representation of its name to use with checked_call, like so:

#define NAMED_FUNCTION(func) func, #func

int main(){
    checked_call(NAMED_FUNCTION(failingFunction));
}

We could also adapt the definition of checked_call to return ret if we wanted to, by changing the return type from void to auto and adding a return ret; if it doesn't throw. However, you may need to examine your functions' actual signature to figure out where the true return value is, assuming the nominal return value is actually an error code.

  • Related