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");
va_end(va);
}
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
tomake_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);
va_end(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).