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.