If you are going to use C++ variadic templates, there is very little use for C-style printf formatting (which is not type-safe). You should fully embrace variadic templates (one of the main reasons variadic templates were invented was to get rid of the type-unsafe nature of the printf-style functions).
For example:
void log_impl(std::ostream&) {}
template<typename T, typename ... Args>
void log_impl(std::ostream &os, const T &first_arg, const Args&... args) {
os << first_arg;
log_impl(os, args...);
}
template<typename ... Args>
void log(uint8_t level, const Args&... args) {
std::ostringstream msg;
log_impl(msg, args...);
callback->dosomething(level, msg.str().c_str()); // some api
}
log(level, "Hello ", "World", '!', " ", 12345);
Online Demo
Or, in C++17 and later, you can use a fold expression to simplify the above code:
template<typename ... Args>
void log(uint8_t level, const Args&... args) {
std::ostringstream msg;
(msg << ... << args);
callback->dosomething(level, msg.str().c_str()); // some api
}
log(level, "Hello ", "World", '!', " ", 12345);
Online Demo
But, that being said, if you absolutely need to use format strings, and you are using C++20 or later, then you should consider using std::format() (or the {fmt} library) instead, eg:
template<typename ... Args>
void log(uint8_t level, std::string_view fmt_str, Args&&... args) {
std::string msg = std::vformat(fmt_str, std::make_format_args(args...));
callback->dosomething(level, msg.c_str()); // some api
}
log(level, "{} {}! {}", "Hello", "World", 12345);
Online Demo