0

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.

6
  • in c++ you should write inline functions instead, you can overload those Commented Jun 9, 2022 at 22:31
  • Problem with inline function - it would be overly complicated to write generic one, as how to pass function name, and all arguments. Macro is very simple solution. If you mean I will have to overload each separate function with checked wrapper, this is pretty much the same as call every one with check. Commented Jun 9, 2022 at 22:32
  • 1
    No, it's not complicated at all. That's what templates are for. And they'll end up doing pretty much the same thing as an old-style macro, except with full type-safety and access to all of core C++ features. Commented Jun 9, 2022 at 22:37
  • 1
    Is there a way to perform the stringification the macro does in a template? My attempt at implementing this as a template relies on the slightly ugly hack of passing a function name as an argument. Commented Jun 9, 2022 at 22:52
  • @SamVarshavchik "No, it's not complicated at all." sure, why you do not post an example as an answer? Commented Jun 10, 2022 at 1:09

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Or #define CHECKED_CALL( function, ... ) checked_call( function, #function, __VA_ARGS__ ) instead
"assuming the nominal return value is actually an error code" no you cannot get error code as return value - exception would be thrown and return will never happen. Intention is to use it with something lilke ::open() system function which returns -1 in error, but actual value otherwise.
If the idea is to use it with a function where ret < 0 represents an error that should be turned into an exception and ret >= 0 represents a genuine return value, return ret; at the end of checked_call seems like what you want.
I know, I mean you may want to put it into your answer, as it is not only for me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.