How to use GCC's printf format attribute with C++11 variadic templates?

I don't believe you can. I bet that GCC only verifies the format string if it's a literal. This is why putting the format attribute on true_log doesn't work - that function is called with what looks (syntactically) like a runtime-determined string. Putting it on log directly would circumvent that, but would require format attributes to support variadic template, which you proved it doesn't.

I suggest that you look at more C++-ish ways to do formatted output. There is, for example, boost::format which works kind of like printf, but dynamically verifies that the number and types of the parameters types match the format string. It doesn't use variadic templates, though, but instead consumes parameters fed to it (via operator %) one-by-one.


For the record, I ended up removing the C++11 variadic templates altogether, and using a traditional 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
}

There is a workaround if you are willing to use a macro.

There are constructs that will cause the compiler to do the checking for you, but will not generate any called code. One such construct is sizeof. So, you could use a macro for your logger to pass the arguments to printf directly but in the context of a sizeof calculation, and then call the logger itself.

The reason to use a macro is to make sure the format string is treated just like a string literal would be treated.

In the illustration below, I treat the sizeof calculation as a throwaway argument, but there should be other ways to apply the same technique.

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__)

Try it online!

Of course, this is a workaround. There are numerous reasons not to use a macro. And in this case, the log macro would interfere with any other function or method with the same name.