3

I have a printf-style function that takes a variable number of arguments. Here is my starting point:

#include <stdio.h>
#include <stdarg.h>
void MyPrint (const char* fmt,...)
  {
  va_list arglist ;
  va_start (arglist, fmt) ;
  vprintf (fmt, arglist) ;
  va_end (arglist) ;
  }

int main()
  {
  MyPrint ("Hello, %s\n", "world") ;
  }

This prints Hello, world as expected.

Now I want to make two changes. First, I want to check the format string using the format attribute of g++. So I declare the MyPrint function first (I have to declare it first, because for some reason g++ doesn't let you assign attributes to a function definition):

void MyPrint (const char* fmt,...) __attribute__ ((format (printf, 1, 2))) ;

Now if I try e.g. MyPrint ("Hello, %d\n", "world") ; I get a nice error message.

The second change I want to make is to use a variadic template parameter. Like this:

#include <utility> // for std::forward
template<typename...Params>
void MyPrint (Params&&... fmt)
  {
  printf (std::forward<Params> (fmt)...) ;
  }

This works too. So I combine the two, by adding the format-checking attribute to the variadic function template with this forward declaration:

template<typename...Params>
void MyPrint (Params&&... fmt) __attribute__ ((format (printf, 1, 2))) ;

But now I get this error message (gcc 10.2):

<source>: In substitution of 'template<class ... Params> void MyPrint(Params&& ...) [with Params = {const char (&)[11], const char (&)[6]}]':
<source>:15:38: required from here
<source>:8:6: error: 'format' attribute argument 2 value '1' refers to parameter type 'const char (&)[11]'

This has got me completely baffled. Can anybody tell me what I'm doing wrong?

Here is the complete program:

#include <stdio.h>
#include <utility> // for std::forward

template<typename...Params>
void MyPrint (Params&&... fmt) __attribute__ ((format (printf, 1, 2))) ;

template<typename...Params>
void MyPrint (Params&&... fmt) // <-- Line 8
  {
  printf (std::forward<Params> (fmt)...) ;
  }

int main()
  {
  MyPrint ("Hello, %s\n", "world") ; // <-- Line 15
  }
2
  • Does std::forward make any sense here? It's going to be copied (and even promoted) to printf anyway. The second change I want to make is to use a variadic template parameter what for? What's the goal here? Commented Feb 28, 2021 at 12:38
  • @KamilCuk: "What's the goal here?" The function is actually a class constructor. I want derived classes to have similar variadic constructors, that can simply call the base class's constructor 'as is'. Commented Feb 28, 2021 at 13:33

3 Answers 3

2

You can make the first error go away by adding a fixed const char * argument as the format string and pointing the attribute to that.

template<typename...Params>
void MyPrint (const char * format, Params&&... fmt) __attribute__ ((format (printf, 1, 2))) ;

template<typename...Params>
void MyPrint (const char * format, Params&&... fmt) // <-- Line 9
  {
  printf (format, std::forward<Params> (fmt)...) ;
  }

Which reveals another error:

test.cc:8:6: error: ‘format’ attribute argument 3 value ‘2’ does not refer to a variable argument list
    8 | void MyPrint (const char * format, Params&&... fmt) // <-- Line 9
      |      ^~~~~~~

It seems that the attribute for checking the printf archetype relies on one const char * argument and a variable argument list and is not willing to work without them. So you have to give up either the C++ template magic or the compile-time format string checking.

Sign up to request clarification or add additional context in comments.

2 Comments

Is there a logical reason that this is impossible, do you think? Or is it just a shortcoming of the compiler? (I tried various compilers, but they all reject my code.)
I don't think it's impossible, it's just not supported. If you have a compelling argument why this would be useful, you can try raising it as a wishlist bug against gcc.
0

Just for info: this has been fixed in gcc 13.1. The program works as expected, with no compiler errors or warnings.

Comments

0

Do not use printf. Use C++23 std::print() or C++20 std::format()

The following code can be compiled in clang 18 with -std=c++23 -stdlib=libc++.

#include <print>  // C++23
#include <utility>

template <typename... Args>
void my_print(std::format_string<Args...> fmt, Args&&... args) {
    std::print(fmt, std::forward<Args>(args)...);
}

int main() {
    my_print("Hello, {}\n", "world");
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.