How do you convert format/va_list to std::string? (How do you use vsnprintf/_s?)
Suppose a third-party library has a logging callback with a signature of:
using LogCallback = void (*)(const char* fmt, va_list ap);
and you need to provide a callback that passes the log message to a function that requires a std::string
:
void PrintLogMessage(const std::string& message);
I assume you need to use one of the vsprintf family of functions:
std::string VaList2String(const char* fmt, va_list ap) {
/* ??? something with vnsprintf or vnsprintf_s ??? */
}
void MyLogCallback(const char* fmt, va_list ap) {
std::string message = VaList2String(format, ap);
PrintLogMessage(message);
}
What is the correct (portable and secure) way to implement VaList2String
in the above that is compatible with all the major platforms/implementations?
Solution 1:
void MyLogCallback(const char* fmt, va_list ap) {
std::string message;
va_list ap_copy;
va_copy(ap_copy, ap);
size_t len = vsnprintf(0, 0, fmt, ap_copy);
message.resize(len + 1); // need space for NUL
vsnprintf(&message[0], len + 1,fmt, ap);
message.resize(len); // remove the NUL
PrintLogMessage(message);
}
A va_list
is commonly actually an array under the hood, so the ap
being passed in as an argument is really a pointer. We need va_copy
to make a copy of the pointed at va_list so we can traverse it twice.
Note that you could probably get away without the + 1 in the (first) resize and then would not need the second resize at all, but it would technically be undefined behavior as you're violating std::string's constraints by overwriting the terminal NUL that it maintains with another NUL.