代码之家  ›  专栏  ›  技术社区  ›  Danra Bathsheba

如何使用可选的格式化消息实现符合标准的断言宏?

  •  2
  • Danra Bathsheba  · 技术社区  · 6 年前

    使用可选的格式化消息实现标准兼容的断言宏的方法是什么?

    我所拥有的在Clang中有效,但(正确地)触发了 -Wgnu-zero-variadic-macro-arguments 打开时发出警告(例如通过 -Wpedantic )当宏在没有可选消息的情况下使用时。 Wandbox

    #define MyAssert(expression, ...)                                      \
        do {                                                               \
            if(!(expression))                                              \
            {                                                              \
                printf("Assertion error: " #expression " | " __VA_ARGS__); \
                abort();                                                   \
            }                                                              \
        } while(0)
    
    3 回复  |  直到 6 年前
        1
  •  1
  •   yairchu    6 年前

    我有一个解决办法,我并不特别自豪……

    我们可以使用以下方法获得纯格式和字符串形式的第一个参数:

    #define VA_ARGS_HEAD(N, ...) N
    #define VA_ARGS_HEAD_STR(N, ...) #N
    

    注意,在使用中,为了不获得警告,应该这样做 VA_ARGS_HEAD(__VA_ARGS__, ) (额外) , 这样 VA_ARGS_HEAD 从不与单个参数一起使用(技巧来自 StoryTeller's answer )

    我们定义以下助手函数:

    #include <stdarg.h>
    #include <stdio.h>
    
    inline int assertionMessage(bool, const char *fmt, ...)
    {
        int r;
        va_list ap;
        va_start(ap, fmt);
        r = vprintf(fmt, ap);
        va_end(ap);
        return r;
    }
    

    当断言具有格式字符串时,函数将使用 __VA_ARGS__ 然而,当 bool 是唯一的参数,我们缺少格式字符串。这就是为什么我们要在 瓦尔阿格斯基 调用它时:

    #define MyAssert(...)                                                          \
        do {                                                                       \
            if(!(VA_ARGS_HEAD(__VA_ARGS__, )))                                     \
            {                                                                      \
                printf("Assertion error: %s | ", VA_ARGS_HEAD_STR(__VA_ARGS__, )); \
                assertionMessage(__VA_ARGS__, "");                                 \
                abort();                                                           \
            }                                                                      \
        } while(0)
    

    注意 assertionMessage 没有 printf 以它的名义。这是故意的,旨在避免编译器为其调用额外的 "" 参数。这样做的缺点是,当格式字符串有帮助时,我们不会得到与格式字符串相关的警告。

        2
  •  3
  •   StoryTeller - Unslander Monica    6 年前

    我们需要真正地使用最大值的预处理器,以便将没有其他参数与它们所在的情况区分开来。但是有了boost.pp,你就可以做到:

    #include <boost/preprocessor/variadic/size.hpp>
    #include <boost/preprocessor/arithmetic/sub.hpp>
    #include <boost/preprocessor/logical/bool.hpp>
    #include <boost/preprocessor/cat.hpp>
    
    
    #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 必须接受至少一个参数(标准)。然后我们计算参数,减去一个,然后转到布尔值(0或1)。此0或1连接到令牌 MY_ASSERT 为了形成一个宏名,我们继续将参数转发到这个宏名。

    MY_ASSERT1 (使用args)是原始宏。 MY_ASSERT0 用…代替自己 MY_ASSERT1(expr,) 尾随逗号意味着我们传递了另一个参数(因此满足了对一个额外参数的要求),但它是一个空的标记序列,因此它什么也不做。

    你可以 see it live .


    既然我们已经走下了这个兔子洞,如果你不想拉进Boost.pp,以上可以用通常的论点计数技巧来完成,稍微调整一下。首先,我们必须决定允许的参数的最大限制。我选择了20个,你可以选择更多。我们需要典型的 CONCAT 宏,这里是这个宏:

    #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
    

    这是一场争论,但有点扭曲。什么时候? __VA_ARGS__ 是一个单一的参数(没有额外的参数),即 N 解析为0。否则,将其解析为1。表达式后面最多可以有20个额外参数,其中任何数量的参数都将解析为相同的1。现在我们把它插到之前使用过Boost的地方:

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

    You can tinker with it here

        3
  •  1
  •   Matthieu Brucher    6 年前

    基本的解决方案是 << 关于CERP:

    #define MyAssert(expression, msg)                                  \
    do {                                                               \
        if(!(expression))                                              \
        {                                                              \
            std::cerr << msg;                                          \
            abort();                                                   \
        }                                                              \
    } while(0)
    

    这个解决方案使用C++流,所以你可以按照你认为合适的格式格式化输出。实际上,这是一个简化的C++ 17解决方案,我用它来避免暂时性的(人们倾向于使用)。 + 而不是 << 使用此解决方案,会触发一些效率警告)。

    然后这样使用:

    MyAssert(true, "message " << variable << " units");
    

    我认为这里的可选性是伪造的,因为您输出的是“断言错误:”,这意味着您需要一条消息。