How to implement a standard-compliant assert macro with an optional formatted message?

后端 未结 3 1460
北海茫月
北海茫月 2021-01-22 01:55

What\'s the way to implement a standard-compliant assert macro with an optional formatted message?

What I have works in clang, but (correctly) triggers the -Wgnu-z

3条回答
  •  孤城傲影
    2021-01-22 02:05

    One needs to really use the preprocessor to the max in order to differentiate no additional arguments from the case where they are present. But with Boost.PP one can do this:

    #include 
    #include 
    #include 
    #include 
    
    
    #define MyAssert(...) BOOST_PP_CAT(MY_ASSERT,BOOST_PP_BOOL(BOOST_PP_SUB(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)))(__VA_ARGS__)
    
    #define MY_ASSERT0(expr) MY_ASSERT1(expr,)
    
    #define MY_ASSERT1(expression, ...)                                    \
        do {                                                               \
            if(!(expression))                                              \
            {                                                              \
                std::printf("Assertion error: " #expression " | " __VA_ARGS__); \
                std::abort();                                              \
            }                                                              \
        } while(0)
    

    MyAssert must accept at least one argument (standard). Then we count the arguments, subtract one, and turn to a boolean (0 or 1). This 0 or 1 is concatenated to the token MY_ASSERT to form a macro name, to which we proceed to forward the arguments.

    MY_ASSERT1 (with args), is your original macro. MY_ASSERT0 substitutes itself with MY_ASSERT1(expr,), the trailing comma means we pass another argument (thus fulfilling the requirement for the one extra argument), but it is an empty token sequence, so it does nothing.

    You can see it live.


    Since we already went down this rabbit hole, if one doesn't want to pull in Boost.PP the above can be accomplished with the usual argument counting trick, slightly adapted. First, we must decide on a maximum limit for the arguments we allow. I chose 20, you can choose more. We'll need the typical CONCAT macro, and this macro here:

    #define HAS_ARGS(...) HAS_ARGS_(__VA_ARGS__,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,)
    #define HAS_ARGS_(a1,a2,a3,a4,a5,b1,b2,b3,b4,b5,c1,c2,c3,c4,c5,d1,d2,d3,d4,d5,e, N, ...) N
    

    It's argument counting, but with a twist. When __VA_ARGS__ is a single argument (no extra ones), the N resolved as 0. Otherwise, it is resolved as 1. There can be up to 20 extra arguments after the expression, any number of which will resolve to the same 1. Now we just plug it into the same place we used boost before:

    #define MyAssert(...) CONCAT(MY_ASSERT, HAS_ARGS(__VA_ARGS__))(__VA_ARGS__)
    

    You can tinker with it here

提交回复
热议问题