How to convert a C-type variadic to C++ style variadic type?

Viewed 122

I have a function which currently looks like this:

void log(uint8_t level, const char* fmt, ...) {
    va_list va;
    va_start(va, fmt);
    char msg[128];
    int  msg_size = vsnprintf(msg, 128, fmt, va);
    va_end(va);
    callback->dosomething(level, msg); // some api
}

I want to convert it to,

void log(uint8_t level, const char* fmt, Args... args) {
    va_list va;
    va_start(va, fmt);
    char msg[128];
    int  msg_size = vsnprintf(msg, 128, fmt, va); // do some things similar here with args
    va_end(va);
    callback->dosomething(level, msg); // some api
}

Can someone help me with that?

2 Answers

This would be one way of doing it:

template<class... Args>
void log(uint8_t level, const char* fmt, Args&&... args) {
    char msg[128];
    int  msg_size = std::snprintf(msg, 128, fmt, std::forward<Args>(args)...);
    callback->dosomething(level, msg); // some api
}

Note that I'm using snprintf instead of the va_list specific vsnprintf.

Demo

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

Related