Printf wrapper in C++

Im trying to build a sick function, it's basically a wrapper around printf.

I need to build a char* buffer with vsnprintf. This is the signature of the function that does that: char* make_buffer_fmt(char foo, const char* fmt, ...)

Now, there is, of course, another function wrapping around this function: int write_fmt(int fd, char foo, const char* fmt, ...)

And another one wrapping around write_fmt: int log(const char* fmt, ...)

How do pass on the parameter pack from log to make_buffer_fmt? I don't want grab the va_list 'manually' everytime. (I fear there is no other way?)

You're probably gonna tell me parameter pack and va_list don't mix well but I'm kinda new to C++ and I'm honestly already glad when it compiles sometimes. The way I have it now, I get the va_list where I need it, in make_buffer_fmt but, of course it just reads some random memory because the va_list is not passed on.

I like the three dots, because it copies the printf behaviour, which is ultimately the goal. When I change the ... to va_list args, it doesn't want my char* anymore :(.

Edit 2: I think my question wasn't that clear so here is some code:

int log_info(const char *fmt, ...) { // Is this how I have to do it? 
    va_list va;                      // Problem I have with this, it that write_fmt doesn't have the '...'
    va_start(va, fmt);               // Instead, I have to pass the va_list to write_fmt, which is not what I want. 
    return write_fmt(LOG_FILENO, LOG_INFO, fmt, va); // Because I want to be able to call write_fmt like this: write_fmt(LOG_FILENO, LOG_INFO, "Hello %s", "World");

Edit: Here is the solution but it's ugly and impractical and a lot of boilerplate code: Passing variable arguments to another function that accepts a variable argument list


  • How do pass on the parameter pack from log to make_buffer_fmt?

    To start with, you need to have a parameter pack.

    This is a C++ variadic function template with a parameter pack:

    template <typename... Args>
    int log(const char *fmt, Args&&... args)
        return write_fmt(LOG_FILENO, LOG_INFO, fmt, std::forward<Args>(args)...);

    You don't use va_list at all, because that's for dealing with C-style variadic function parameters after you've lost all the type information. In C++, you don't need to discard that type information in the first place (or at least not until you call a C-style function like vsnprintf)

    The only (potential) disadvantage is that all these functions must be defined inline in the header. Since they're not really doing anything that seems worth hiding, I don't think that's a problem here.

    Note that in your original version, int write_fmt(int fd, char foo, const char* fmt, ...) isn't suitable for calling with a va_list parameter anyway. This isn't like the *args parameter sequence in Python, it can't be expanded back into a variable-length parameter list. It's just a single struct.

    That's why the standard library has both snprintf and vsnprintf:

    int snprintf(char* buffer, std::size_t buf_size, const char* format, ...);
    int vsnprintf(char* buffer, std::size_t buf_size, const char* format, std::va_list vlist);

    Anyway, write_fmt should be a similar perfect-forwarding passthrough to the above, and then make_buffer_fmt will either call snprintf with its expanded parameter pack:

    template <typename... Args>
    int make_buffer_fmt(char *buffer, size_t bufsize, const char *fmt, Args&&... args)
        return snprintf(buffer, bufsize, fmt,  std::forward<Args>(args)...);

    or vsnprintf with an explicit va_list (although there's really no benefit to doing this)

    int make_buffer_fmt(char *buffer, size_t bufsize, const char *fmt, ...)
        va_list va;
        va_start(va, fmt);
        int rc = vsnprintf(buffer, bufsize, fmt, va);
        return rc;

    or std::format if you're ready to embrace the 21st century, and don't mind changing the format string syntax (or the dynamic allocation).