C++ variable arguments with std::string only
Solution 1
First, it's buffer.c_str()
and format.c_str()
(note the parentheses).
Second, the first parameter of vsprintf
should be a modifiable buffer of sufficient size. You are trying to pass a const char*
pointing to a buffer just one byte large.
You could use vector<char>
as a buffer holder (it's easy to resize). The problem is, there's no way to get the required buffer size out of vsprintf
. One technique is to allocate some initial buffer, then call vsnprintf
(note the 'n') repeatedly, doubling the size of the buffer every time the function says it's too small.
Solution 2
A Production Quality Answer
#include <cstdarg>
#include <string>
#include <vector>
// requires at least C++11
const std::string vFormat(const std::string sFormat, ...) {
const char * const zcFormat = sFormat.c_str();
// initialize use of the variable argument array
va_list vaArgs;
va_start(vaArgs, sFormat);
// reliably acquire the size from a copy of
// the variable argument array
// and a functionally reliable call
// to mock the formatting
va_list vaCopy;
va_copy(vaCopy, vaArgs);
const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaCopy);
va_end(vaCopy);
// return a formatted string without
// risking memory mismanagement
// and without assuming any compiler
// or platform specific behavior
std::vector<char> zc(iLen + 1);
std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
va_end(vaArgs);
return std::string(zc.data(), zc.size()); }
#include <ctime>
#include <iostream>
#include <iomanip>
// demonstration of use
int main() {
std::time_t t = std::time(nullptr);
int i1 = 11; int i2 = 22; int i3 = 33;
std::cerr
<< std::put_time(std::localtime(& t), "%D %T")
<< vFormat(" [%s]: %s {i1=%d, i2=%d, i3=%d}",
"DEBUG",
"Xyz failed",
i1, i2, i3)
<< std::endl;
return 0; }
Related videos on Youtube
Comments
-
phew over 1 year
I'm trying to create a function that takes a variable amount of std::string arguments and formats a string with it.
Example:
Test::formatLine(const string::format, ...) { const std::string buffer; va_list args; va_start(args, format); vsprintf(buffer.c_str, format.c_str, args); va_end(args); cout << buffer << endl; }
Compiling this snippet errors:
Error 1 error C3867: 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str': function call missing argument list; use '&std::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str' to create a pointer to member
What I want to achieve:
Test t = Test(); t.formatLine("Hello %s!", "monsieur");
should print
Hello monsieur!
t.formatLine("Hello %s %s! How %s you today?", "good", "sir", "are");
should print
Hello good sir! How are you today?
Is it even possible to use
va_list
andvsprintf
withstd::string
only, avoidingchar buffer[size]
?Working example (so far) with fixes suggested by Igor, using buffer:
void Test::formatLine(string format, ...) { char buffer[256]; va_list args; va_start(args, format); vsprintf_s(buffer, format.c_str(), args); va_end(args); cout << buffer << endl; }
Using Igor Tandetnik's suggestion and sample code I finally got a working example that does not use char buffer[size]:
void Test::formatLine(string format, ...) { vector<char> buf(256); va_list args; va_start(args, format); vsnprintf_s(&buf[0], buf.size(), buf.size() + strlen(format.c_str()), format.c_str(), args); va_end(args); cout << &buf[0] << endl; }
-
Xeo over 10 yearsOnly trivial types are allowed to be passed to
...
,std::string
is no such type.
-
-
phew over 10 yearsCould you maybe give me an example on how to use vector<char> in this case? I just can't figure out how to use it with vsnprintf().
-
Igor Tandetnik over 10 years
vector<char> buf(1024); vsnprintf(&buf[0], buf.size(), format.c_str(), args);
. Resizing the buffer is left as an exercise for the reader. -
Praetorian over 10 years@phew
vsnprintf
is the right idea, but you shouldn't have to call it repeatedly. Makebuffer
avector<char>
and callvsnprint(&buffer[0], 0, format.c_str(), args);
. If the return value is non-negative, callbuffer.resize(retval+1);
and thenvsnprintf(&buffer[0], retval+1, format.c_str(), args);
-
Igor Tandetnik over 10 years@Praetorian: Interesting. I was going by MSDN documentation, which states that
vsnprintf
returns the number of characters written if successful, and -1 when buffer is too small. I've now checked the C99 standard, which says thatvsnprintf
returns the required size of the buffer, and thus can be used to measure it in advance. I wonder (but am too lazy to check) whether MSVC implementation is non-conforming, or whether the documentation is wrong. -
Praetorian over 10 years@phew And make sure you give the vector an initial size of at least 1, otherwise
&buffer[0]
will be undefined behavior. -
Praetorian over 10 years@Igor Yeah that does seem non-conforming. I don't have MSVC installed on this machine (and probably would be too lazy myself to test anyway :)) to verify whether the implementation or documentation is broken.
-
Igor Tandetnik over 10 years@Praetorian: Also, per C99, the first parameter may be
NULL
if the second is 0, so one can dovsnprint(NULL, 0, format.c_str(), args);
. This removes the concern of doing&buf[0]
on a zero-sized vector. Also, one shouldn't pass the sameva_list
value twice to twovsnprintf
calls; per 7.15p3, the value becomes indeterminate after the first call. One must useva_start
again, or else make a copy withva_copy
beforehand. -
phew over 10 years@Praetorian I'm not sure if that is a proper solution but I just EDITed the post and added a working function - is this the way to go?