2

I'm trying to combine gcc's compile-time checking of printf format strings with c++11's variadic template packs.

I know I can decorate a variadic function with gcc's __attribute__((format(__printf__, ...)) decorator.

I know I can expand a variadic template pack into a variadic function. eg: printf(fmt, pack...);

Is it possible to combine the two?

In this question the accepted answer notes that gcc requires literals for the format check to work.

In the example below a const char array is passed to both the variadic function and the variadic function template. gcc can tell it is a literal when check is called in main, but not from check_t

Is there any way to pass the fmt string to check_t in such a way that gcc sees it as a literal?

Working example of what I'm trying to achieve:

#include <iostream>

void check(const char*, ...) __attribute__((format(__printf__, 1, 2)));
void check(const char*, ...)
{ 
    // exists only for the purposes of catching bogus format strings
}

template<typename... Ts>
void check_t(const char fmt[], Ts... ts)
{
    // check the format string by expanding ts into the check function
    check(fmt, ts...);
}

int main()
{
    const char f[] = "foo %s";
    check_t(f, 5);              // compiles
    check(f, 5);                // fails to compile
    return 0;
}
Community
  • 1
  • 1
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213

2 Answers2

2

You may pass the c-string in template argument:

template<const char* fmt, typename... Ts>
void check_t(Ts... ts)
{
    // check the format string by expanding ts into the check function
    check(fmt, ts...);
}

constexpr const char f[] = "foo %s";

int main()
{
    check_t<f>(5); // fails to compile
    //check(f, 5); // fails to compile
    return 0;
}

Live example

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Very nice - but unfortunately I am unable to use this one - the format string has to be passed as a parameter – Steve Lorimer Jul 22 '14 at 09:12
  • `#define CHECK_T(FMT, ...) check_t(__VA_ARGS__)` may help so. – Jarod42 Jul 22 '14 at 09:59
  • I need to be able to pass non-compile-time constants too, so this will unfortunately not work. In the end I came up with something similar which isn't ideal, but will allow me to move forward: `#define CHECK_T(fmt, ...) { check(fmt, __VA_ARGS__); check_t(fmt, __VA_ARGS__); }` – Steve Lorimer Jul 22 '14 at 22:41
  • Somewhat of an enhancement I believe, using the [comma operator](http://en.wikipedia.org/wiki/Comma_operator): `#define CHECK_T(fmt, ...) ( check(fmt, __VA_ARGS__), check_t(fmt, __VA_ARGS__) )` – Steve Lorimer Jul 22 '14 at 22:51
0

As noted in comments, it is not possible to distinguish a string literal from an array of characters in a function template, unless it is a template parameter.

Therefore taking advantage of gcc's __attribute__((format(__printf__, ...)) syntax from within a variadic function template is not possible (unless the format string is a template parameter rather than function parameter as in @Jarod42's answer).

This kind of behaviour is very reminiscent of logging frameworks - you want both the type safety of variadic templates and the compile-time check of the format string.

Workaround: use the comma operator

Although this is not a solution to the particular question, it is a workaround which allows the benefits of both.

It makes use of the comma operator, in particular the example described in the linked article:

i = (a, b); // discards a, stores b into i 

Therefore the macro becomes something like this:

#define CHECK_T(fmt, ...) ( check(fmt, __VA_ARGS__), check_t(fmt, __VA_ARGS__) )
                                       ^           ^              ^
                                       |           |              |
    calls variadic function `check` ---+           |              |
                                                   |              |
    comma operator discards 1st operand -----------+              |
                                                                  |
    returns result of variadic function template `check_t` -------+

Even though the the check call will invalid format strings during compilation, my assumption is that it will actually be compiled out of the release version of the binary because it is an empty function, leaving only the check_t call which does the actual logging.

Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213