Home > Software design >  Error check macro that calls function in destructor
Error check macro that calls function in destructor

Time:02-01

I'm using an external C API from C , where functions report an error by setting a global error state that needs to be queried manually after each function call.

In order to avoid doing this manually each time I'm using a macro to wrap the function calls, which looks similar to this:

#define CHECK(_call_) \
    do { \
        _call_; \
        int err = LastError(); \
        if(err != 0) throw Error(err, __FILE__, __LINE__); \
    } while(0)    

used like CHECK(ExternalFct(1, 2, 3)).

But this does not work for functions that return a value. In order to support this, a solution that seemed to work is to create and call a temporary lambda, containing an object that checks the error in its destructor:

#define CHECK(_call_) \
    ([&] { \
        struct ErrorChecker { \
            ~ErrorChecker() noexcept(false) { \
                int err = LastError(); \
                if(err != 0) throw Error(err, __FILE__, __LINE__); \
            } \
        } errorChecker; \
        return _call_; \
    }())

It uses the fact that return <expression> is allowed even if the expression is void.

This seems to work, but looks like it could cause problems, mainly due to the capture-all lambda (needed to allow local variables inside the call expression), and the destructor which throws exceptions.

Are there situations in which this construct would cause problems (or unnecessary overhead), and is there a better way to make a single CHECK() macro that would work with both void and non-void functions?

CodePudding user response:

I like your variant better, but if you want to get rid of destructor, you can do:

#define CHECK(_call_) \
    ([&]<typename T = decltype(_call_)> { \
        if constexpr (std::is_void_v<T>) { \
            _call_; \
            int err = LastError(); \
            if(err != 0) throw Error{err, __FILE__, __LINE__}; \
            return; \
        } else { \
            auto result = _call_; \
            int err = LastError(); \
            if(err != 0) throw Error{err, __FILE__, __LINE__}; \
            return result; \
        } \
    }())

CodePudding user response:

If you stick with macros, I would suggest renaming CHECK() to something more like CHECK_NO_RETURN(), and then defining a separate macro to handle returns, ie:

#define CHECK_NO_RETURN(_call_) \
    do { \
        _call_; \
        int err = LastError(); \
        if(err != 0) throw Error(err, __FILE__, __LINE__); \
    } while(0)    

#define CHECK_THEN_RETURN(_call_) \
    do { \
        auto ret = _call_; \
        int err = LastError(); \
        if(err != 0) throw Error(err, __FILE__, __LINE__); \
        return ret; \
    } while(0) 
CHECK_NO_RETURN(ExternalFct1(1, 2, 3));
CHECK_THEN_RETURN(ExternalFct2(1, 2, 3));

Otherwise, I would suggest changing CHECK() into a variadic template function instead, eg:

#include <type_traits>

template<typename R, typename... Args>
inline typename std::enable_if<!std::is_void<R>::value, R>::type
Check(R (&call)(Args...), Args... args)
{
    R ret = call(args...);
    int err = LastError();
    if (err != 0) throw Error(err, __FILE__, __LINE__);
    return ret;
}

template<typename... Args>
inline void Check(void (&call)(Args...), Args... args)
{
    call(args...);
    int err = LastError();
    if (err != 0) throw Error(err, __FILE__, __LINE__);
}
Check(ExternalFct1, 1, 2, 3);
return Check(ExternalFct2, 1, 2, 3);

Online Demo

Alternatively, in C 17 and later:

#include <type_traits>

template<typename R, typename... Args>
inline auto Check(R (&call)(Args...), Args... args)
{
    if constexpr (!std::is_void_v<R>)
    {
        R ret = call(args...);
        int err = LastError();
        if (err != 0) throw Error(err, __FILE__, __LINE__);
        return ret;
    }
    else
    {
        call(args...);
        int err = LastError();
        if (err != 0) throw Error(err, __FILE__, __LINE__);
    }
}

Online Demo

  • Related