如何使用可选的格式化消息实现符合标准的断言宏?
How to implement a standard-compliant assert macro with an optional formatted message?
使用可选格式化消息实现符合标准的断言宏的方法是什么?
我在 clang 中的工作,但如果在没有可选消息的情况下使用宏时打开(例如通过 -Wpedantic
),(正确地)触发 -Wgnu-zero-variadic-macro-arguments
警告。 Wandbox
#define MyAssert(expression, ...) \
do { \
if(!(expression)) \
{ \
printf("Assertion error: " #expression " | " __VA_ARGS__); \
abort(); \
} \
} while(0)
人们需要真正最大限度地使用预处理器,以便区分没有其他参数和它们存在的情况。但是使用 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
(带参数)是您的原始宏。 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__)
基本解决方案是在 cerr:
上使用 <<
#define MyAssert(expression, msg) \
do { \
if(!(expression)) \
{ \
std::cerr << msg; \
abort(); \
} \
} while(0)
此解决方案使用 C++ 流,因此您可以根据需要格式化输出。实际上,这是我用来避免临时变量的 C++17 解决方案的简化(人们倾向于在该解决方案中使用 +
而不是 <<
,从而触发一些效率警告)。
然后像这样使用它:
MyAssert(true, "message " << variable << " units");
我认为这里的可选性是假的,因为你正在输出 "Assertion error:" 意味着你期待一条消息。
我有一个我并不特别引以为豪的解决方案..
我们可以使用以下方法以纯形式和字符串形式获取第一个参数:
#define VA_ARGS_HEAD(N, ...) N
#define VA_ARGS_HEAD_STR(N, ...) #N
请注意,在使用中,为了不收到警告,您应该执行 VA_ARGS_HEAD(__VA_ARGS__, )
(带有额外的 ,
),这样 VA_ARGS_HEAD
永远不会与单个参数一起使用(技巧取自 ).
我们定义如下辅助函数:
#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
。这是故意的,旨在避免编译器在使用额外 ""
参数调用时给出与格式字符串相关的警告。这样做的缺点是,当它们有用时,我们不会收到与格式字符串相关的警告。
使用可选格式化消息实现符合标准的断言宏的方法是什么?
我在 clang 中的工作,但如果在没有可选消息的情况下使用宏时打开(例如通过 -Wpedantic
),(正确地)触发 -Wgnu-zero-variadic-macro-arguments
警告。 Wandbox
#define MyAssert(expression, ...) \
do { \
if(!(expression)) \
{ \
printf("Assertion error: " #expression " | " __VA_ARGS__); \
abort(); \
} \
} while(0)
人们需要真正最大限度地使用预处理器,以便区分没有其他参数和它们存在的情况。但是使用 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
(带参数)是您的原始宏。 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__)
基本解决方案是在 cerr:
上使用<<
#define MyAssert(expression, msg) \
do { \
if(!(expression)) \
{ \
std::cerr << msg; \
abort(); \
} \
} while(0)
此解决方案使用 C++ 流,因此您可以根据需要格式化输出。实际上,这是我用来避免临时变量的 C++17 解决方案的简化(人们倾向于在该解决方案中使用 +
而不是 <<
,从而触发一些效率警告)。
然后像这样使用它:
MyAssert(true, "message " << variable << " units");
我认为这里的可选性是假的,因为你正在输出 "Assertion error:" 意味着你期待一条消息。
我有一个我并不特别引以为豪的解决方案..
我们可以使用以下方法以纯形式和字符串形式获取第一个参数:
#define VA_ARGS_HEAD(N, ...) N
#define VA_ARGS_HEAD_STR(N, ...) #N
请注意,在使用中,为了不收到警告,您应该执行 VA_ARGS_HEAD(__VA_ARGS__, )
(带有额外的 ,
),这样 VA_ARGS_HEAD
永远不会与单个参数一起使用(技巧取自
我们定义如下辅助函数:
#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
。这是故意的,旨在避免编译器在使用额外 ""
参数调用时给出与格式字符串相关的警告。这样做的缺点是,当它们有用时,我们不会收到与格式字符串相关的警告。