如何在C++ 11可变参数模板中使用GCC的printf格式属性?

Mar*_*nde 15 c++ gcc function-attributes variadic-templates c++11

我有一个C++类,它是日志系统的前端.它的日志记录功能是使用C++ 11的可变参数模板实现的:

template <typename... Args>
void Frontend::log(const char *fmt, Args&&... args) {
  backend->true_log(fmt, std::forward<Args>(args)...);
}
Run Code Online (Sandbox Code Playgroud)

每个日志记录后端都实现自己的版本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..
}
Run Code Online (Sandbox Code Playgroud)

一切都很好,我很高兴.

现在,我想对log()参数添加静态检查:具体来说,我想使用GCC的printf格式属性.

我开始使用标记log()函数__attribute__ ((format (printf, 2, 3)))(因为this第一个"隐藏"参数,我需要将参数索引移动一个).这不起作用,因为如果失败并出现编译错误:

error: args to be formatted is not ‘...’
Run Code Online (Sandbox Code Playgroud)

然后,我尝试将相同的属性添加到该true_log()函数中.它编译,但实际上没有执行错误检查:我试图传递log()一些无效的格式/变量组合,并且没有发出警告.也许这种检查"太晚了",换句话说,有关变量的信息已经在调用链中丢失了?

作为最后的手段,如果我注释log()__attribute__ ((format (printf, 2, 0))),我会收到关于错误的格式字符串警告,但无诊断会为无效的格式/变量组合发出.

总结问题:如果我使用C++ 11的可变参数模板,如何从GCC进行全格式检查?

fgp*_*fgp 5

我不相信你可以。我敢打赌,GCC 仅验证格式字符串(如果它是文字。这就是为什么放置该format属性true_log不起作用的原因 - 该函数是用看起来(语法上)像运行时确定的字符串来调用的。直接放置它log可以避免这一点,但需要format属性来支持可变参数模板,而您证明它不支持。

我建议您查看更多 C++ 风格的方法来进行格式化输出。例如,它boost::format的工作方式有点像 printf,但动态验证参数类型的数量和类型是否与格式字符串匹配。不过,它不使用可变参数模板,而是一一消耗提供给它的参数(通过运算符 %)。


Mar*_*nde 5

作为记录,我最终完全删除了 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
}
Run Code Online (Sandbox Code Playgroud)