For convenient usage I want to write formatting function similar to sprintf just returning std::string, like this:
std::string format_string(const char* format, ...)
I can use vsnprintf
there but have problem - I don't know in advance how long temp buffer should be. On Microsoft have function _vscprintf
that can do it but I think it not portable?
One option is have temp buffer start some known size then increase it if see it's not enough with vsnprintf
. Are there better approach? Thanks
P.S. Please give answer without Boost. I know about Boost, but I'm curious how implement it without.
One option is have temp buffer start some known size then increase it if see it's not enough with
vsnprintf
. Are there better approach? Thanks
You can use vasprintf()
, but that does an unnecessary heap allocation - it's unlikely to be faster on average. Using alloca
you can avoid the heap. Or, you can write directly into the string
that's returned: NRVO should avoid a copy, and as of C++11 move semantics would limit the cost sans-NRVO to a few pointer swaps.
#include <cstdio>
#include <cstdarg>
#include <alloca.h>
#include <string>
#include <iostream>
std::string stringf(const char* format, ...)
{
va_list arg_list;
va_start(arg_list, format);
// SUSv2 version doesn't work for buf NULL/size 0, so try printing
// into a small buffer that avoids the double-rendering and alloca path too...
char short_buf[256];
const size_t needed = vsnprintf(short_buf, sizeof short_buf,
format, arg_list) + 1;
if (needed <= sizeof short_buf)
return short_buf;
// need more space...
// OPTION 1
std::string result(needed, ' ');
vsnprintf(result.data(), needed, format, arg_list);
return result; // RVO ensures this is cheap
OR
// OPTION 2
char* p = static_cast<char*>(alloca(needed)); // on stack
vsnprintf(p, needed, format, arg_list);
return p; // text copied into returned string
}
int main()
{
std::string s = stringf("test '%s', n %8.2f\n", "hello world", 3.14);
std::cout << s;
}
An simpler and initially faster option would be:
std::string result(255, ' '); // 255 spaces + NUL
const size_t needed = vsnprintf(result.data(), result.size() + 1,
format, arg_list);
result.resize(needed); // may truncate, leave or extend...
if (needed > 255) // needed doesn't count NUL
vsnprintf(result.data(), needed + 1, format, arg_list);
return result;
The potential problem is that you're allocating at least 256 characters however short the actual text stored: that could add up and cost you in memory/cache related performance. You might be able to work around the issue using [shrink_to_fit
]http://en.cppreference.com/w/cpp/string/basic_string/shrink_to_fit), but the Standard doesn't require it to actually do anything (the requirements are "non binding"). If you end up having to copy to a new exactly-sized string, you might as well have used the local char array.
C99 introduced snprintf and possibly vsnprintf
as well. There are several portable open source implementations of (v)snprintf
such as this one. The latter also implements vasprintf
which dynamically allocates storage.
Also consider the C++ Format library which provides a safe printf
implementation similar to Boost Format, but much faster.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With