How do I get printf-like warnings with variadic arguments?
Asked Answered
S

0

6

If I compile g++ with -Wformat on the following code, I get a warning, as expected.

int main(int argc, char * argv[])
{
    printf("argc %lld\n", argc);
}

However, if compile the following code, I get no warning.

template <typename... Ts>
void foo(char const * s, Ts && ... ts)
{
    printf(s, std::forward<Ts>(ts)...);
}

int main(int argc, char * argv[])
{
    foo("argc %lld\n", argc);
}

I was a bit surprised, but I thought maybe the references were throwing off the detection, so I tried this.

template <typename... Ts>
void foo(char const * s, Ts ... ts)
{
    printf(s, ts...);
}

int main(int argc, char * argv[])
{
    foo("argc %lld\n", argc);
}

However, even with that, I get no warning. Hmmm.

Since I'm compiling with C++17, I tried this.

template <typename... Ts>
[[gnu::format(printf, 1, 2)]]
void foo(char const * s, Ts ... ts)
{
    printf(s, ts...);
}

int main(int argc, char * argv[])
{
    foo("bar %lld\n", argc);
    printf("bar %lld\n", argc);
}

I still get no warning, but I do get a nice error...

error: 'format' attribute argument 3 value '2' does not refer to a variable argument list

OK, I get it, but if I have to change to a variable argument list, then I lose the type information.


The real issue is that I have a function kinda like this...

template <typename T, typename... ArgTs>
void format(T t, char const * format_str, ArgTs && ... args);

In that function, I do some stuff with T and ArgTs, but I want the same printf style warnings I would get if I were to call printf with the format_str and args. I need the type information provided by the arguments, so I can't just use a variable argument list.

Of course, I can write an empty function that takes a variable argument list, and annotate it with [[gnu::format]], but that behaves the exact same way as calling printf() above, so it only works if called directly with a variable argument list, and does not work if called with a variadic argument pack expansion.

So, how do I get the benefit of [[gnu::format]] with a function that does not take its arguments by variable argument list?

I could use a macro, of course, and have it call a do-nothing function with the [[gnu::format]] attribute, then call the real one, but I don't want to use a macro for this.

Spermic answered 25/2, 2023 at 14:21 Comment(9)
If you can use C++20, there is std::print and std::format which give you printf like syntax with C++ variadic templates and type safety for printing and string creation respectively.Mourning
godbolt.org/z/Th36nPnq9Warship
godbolt.org/z/aq5KndYd8Leishmaniasis
So, with the godblot links above, it is of course now clear that it has to do with the visibility of the format string argument. The second requires a char * template, but the first takes a lambda with the value. Made me think the compiler may be able to see through this: godbolt.org/z/7a5jr5MMn but that does not work. The lambda solution seems workable.Spermic
Sadly, I'm constrained to C++17.Spermic
Then use this library : github.com/fmtlib/fmtDrapery
Yeah - we already have a logging library that uses the printf style and it is used all over the place. I don't want to rewrite the thing - I just want to add the ability to warn for misuse. Fortunately (or not, depending on your perspective), most things use some logging macros - so I was able to get the lambda work-around going.Spermic
Clang seems to work properly using [[gnu::format(printf, 1, 2)]].Metameric
@GiovanniCerretani Indeed! ... and it even tells the user about the diff between gcc and clang: "warning: GCC requires a function with the 'format' attribute to be variadic [-Wgcc-compat]" (compiler explorer)Guff

© 2022 - 2025 — McMap. All rights reserved.