如何使用可选的格式化消息实现符合标准的断言宏?
How to implement a standard-compliant assert macro with an optional formatted message?
使用可选的格式化消息实现符合标准的断言宏的方法是什么?
我所拥有的在 clang 中工作,但是(正确)如果在没有可选消息的情况下使用宏时打开(例如通过-Wpedantic
)它会触发-Wgnu-zero-variadic-macro-arguments
警告。魔杖盒
#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,)
替换自身,尾随逗号表示我们传递另一个参数(从而满足一个额外参数的要求),但它是一个空的标记序列,所以它什么也不做。
您可以现场观看。
由于我们已经进入了这个兔子洞,如果不想拉进 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__)
你可以在这里修补它
我有一个我并不特别自豪的解决方案。
我们可以通过以下方式以纯形式和字符串形式获取第一个参数:
#define VA_ARGS_HEAD(N, ...) N
#define VA_ARGS_HEAD_STR(N, ...) #N
请注意,在使用中,为了不收到警告,您应该执行VA_ARGS_HEAD(__VA_ARGS__, )
(使用额外的,
),以便VA_ARGS_HEAD
永远不会与单个参数一起使用(取自StoryTeller的答案的技巧)。
我们定义以下帮助程序函数:
#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
。这是有意为之的,旨在避免编译器使用额外的""
参数为其调用发出与格式字符串相关的警告。这样做的缺点是,当它们有用时,我们不会收到与格式字符串相关的警告。
基本的解决方案是在 cerr 上使用<<
:
#define MyAssert(expression, msg)
do {
if(!(expression))
{
std::cerr << msg;
abort();
}
} while(0)
此解决方案使用C++流,因此您可以根据需要设置输出格式。实际上,这是我用来避免临时使用的 C++17 解决方案的简化(人们倾向于使用此解决方案+
而不是<<
,从而触发一些效率警告)。
然后像这样使用它:
MyAssert(true, "message " << variable << " units");
我认为这里的可选性是假的,因为您正在输出"断言错误:",这意味着您期望一条消息。
- 使用CMake检测支持的C++标准
- 如何理解C++标准N3337中的expr.const.cast子句8
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 编译标准库类型
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 编译器如何在使用SFINAE的函数和标准函数之间确定两者是否可行
- 铸造标准::有没有回到原来的类型
- 标准 N3337 5.2.10 第 7 条中的C++"类型"是什么意思?
- this_thread::sleep_for和计时时钟之间的关系是否由C++11标准指定
- 标准库类型的赋值运算符的引用限定符
- 标准是否严格定义了该程序应该如何编译?
- 如何从Windows应用程序输出到标准?
- 尝试使用 std::vector<std::thread时出现静态断言失败错误>
- 安全到标准:移动会员?
- 如何正确将字符串转换为标准::时间::system_clock::time_point?
- 这是否符合C++标准:双响双响,例如!!(-0.0).
- 标准::变体的赋值运算符
- 捕获标准输出以压缩并使用 CTRL-C 中断会给出损坏的 zip 文件
- 如何使用可选的格式化消息实现符合标准的断言宏?
- 在没有-DNDEBUG和-O3的情况下编译时,标准库实现不使用断言有什么原因吗