如何将 GCC 的 printf 格式属性与 C++11 可变参数模板一起使用?
How to use GCC's printf format attribute with C++11 variadic templates?
我有一个C++类,它是日志系统的前端。它的日志记录功能是使用C++11的可变模板实现的:
template <typename... Args>
void Frontend::log(const char *fmt, Args&&... args) {
backend->true_log(fmt, std::forward<Args>(args)...);
}
每个日志后端都实现了自己版本的true_log
,其中包括使用转发的参数来调用vsnprintf
。例如:
void Backend::true_log(const char *fmt, ...) {
// other stuff..
va_list ap;
va_start(ap, fmt);
vsnprintf(buffer, buffer_length, fmt, ap);
va_end(ap);
// other stuff..
}
一切都很好,我很高兴。
现在,我想添加一个对log()
参数的静态检查:特别是,我想使用GCC的printf format属性。
我首先用__attribute__ ((format (printf, 2, 3)))
标记log()
函数(由于this
是第一个"隐藏"参数,我需要将参数索引移动一)。这不起作用,因为如果由于编译错误而失败:
error: args to be formatted is not ‘...’
然后,我尝试将相同的属性添加到true_log()
函数中。它进行编译,但实际上没有执行错误检查:我试图将一些无效的格式/变量组合传递给log()
,但没有发出警告。也许这种检查"太迟了",或者,换句话说,变量的信息在调用链中丢失了?
最后,如果我用__attribute__ ((format (printf, 2, 0)))
注释log()
,我会收到关于错误格式字符串的警告,但不会对无效的格式/变量组合发出诊断。
总结问题:如果我使用C++11的可变模板,我如何从GCC进行完整格式检查
我认为你做不到。我敢打赌,GCC只有在文本的情况下才会验证格式字符串。这就是为什么将format
属性放在true_log
上不起作用的原因——该函数的调用(语法上)看起来像是运行时确定的字符串。将其直接放在log
上可以避免这种情况,但需要format
属性来支持可变模板,您已经证明了这一点。
我建议您考虑更多C++风格的方法来进行格式化输出。例如,boost::format
的工作方式有点像printf,但它会动态验证参数类型的数量和类型是否与格式字符串匹配。不过,它不使用可变模板,而是逐个使用(通过运算符%)提供给它的参数。
为了记录,我最终完全删除了C++11可变模板,并使用了传统的va_list
。
__attribute__((format(printf, 2, 3)))
void Frontend::log(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
backend->true_log(fmt, ap);
va_end(ap);
}
void Backend::true_log(const char *fmt, va_list ap) {
// log the message somehow
}
如果您愿意使用宏,有一个解决方法。
有些构造会导致编译器为您进行检查,但不会生成任何调用的代码。一个这样的构造是CCD_ 17。因此,您可以使用记录器的宏将参数直接传递给printf
,但要在sizeof
计算的上下文中传递,然后调用记录器本身。
使用宏的原因是确保格式字符串的处理方式与字符串文字的处理方式相同。
在下图中,我将sizeof
计算视为一次性参数,但应该有其他方法来应用相同的技术。
template <typename... Ts>
void Frontend::log(size_t, const char *fmt, Ts&&... args) {
backend->true_log(fmt, std::forward<Ts>(args)...);
}
#define log(...) log(sizeof(printf(__VA_ARGS__)), __VA_ARGS__)
在线试用!
当然,这是一个变通办法。不使用宏的原因有很多。在这种情况下,log
宏将干扰具有相同名称的任何其他函数或方法。
- 在不传递参数数量且只有3个点的情况下,如何使用变差函数
- 如何使用可变参数模板强制转换每个变体类型
- 关于如何在具有单个参数的变体构造中选择替代方案?
- 调用参数排列不变函数 f(i++, i++)
- 参数归纳与标准::变体
- 模板化回调参数的逆变,如 C# 中的逆变
- 如何在没有参数包的情况下编写变差函数
- 通过具有嵌套类的工厂类获取多个变异类模板参数包
- 获取模板参数的成员变量值列表
- 保留短 lambda 用作函数的中间参数,使用 clang 格式保持不变
- 如何定义变体<x,y,z>提取模板参数的子类型
- 正确对齐内存模板,参数顺序不变
- 递归中不同参数类型的变元模板函数
- 通过函数指针传递给变差函数的参数会更改其值
- 提升预定义为带有参数的全局 lambda 的变体访问者
- 使用可变参数模板参数提升变体访问者
- boost ::变体 - 为什么模板参数比const字符串参数具有更高的优先级
- 将变参数包中的值加载到临时数组中
- 使用额外参数提升变体访客
- 正在将动态数组元素解析为参数?(变音符)