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

时间:2018-12-31 10:55:17

标签: c++ macros assert

用可选的格式化消息实现符合标准的断言宏的方式是什么?

我使用clang所做的工作,但是(在正确的情况下)在不使用可选消息的情况下使用宏时(例如,通过-Wgnu-zero-variadic-macro-arguments)打开了-Wpedantic警告。 Wandbox

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

3 个答案:

答案 0 :(得分:3)

一个人需要真正地最大限度地使用预处理器,以便将其他参数与出现的情况区分开来。但是使用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_ASSERT0MY_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

答案 1 :(得分:1)

基本解决方案是在cerr上使用<<

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

此解决方案使用C ++流,因此您可以根据需要格式化输出。实际上,这是我用来避免临时使用的C ++ 17解决方案的简化形式(人们倾向于在此解决方案中使用+而不是<<,从而触发一些效率警告)。

然后像这样使用它:

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

我认为可选性在这里是虚假的,因为您正在输出“断言错误:”,这意味着您希望收到一条消息。

答案 2 :(得分:1)

我有一个我并不特别自豪的解决方案。

我们可以使用以下格式以字符串形式获取第一个参数:

#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是唯一的参数时,我们缺少格式字符串。这就是为什么我们在调用__VA_ARGS__之后添加另一个空字符串的原因:

#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。这是有意的,旨在避免编译器使用额外的""参数为其调用提供与格式字符串相关的警告。不利的一面是,当它们有用时,我们不会收到与格式字符串有关的警告。