Thursday, May 18, 2006

Formatting std::string

The following snippet has been a part of my personal code library for years. It is useful for formatting a std::string in a traditional printf() way. For all its ills, printf/sprintf() is incredibly convenient. This code is for Win32. Minor modification is required for Unix.


#include <stdio.h>
#include <stdarg.h>
#include <string>

std::string format_arg_list(const char *fmt, va_list args)
{
if (!fmt) return "";
int result = -1, length = 256;
char *buffer = 0;
while (result == -1)
{
if (buffer) delete [] buffer;
buffer = new char [length + 1];
memset(buffer, 0, length + 1);
result = _vsnprintf(buffer, length, fmt, args);
length *= 2;
}
std::string s(buffer);
delete [] buffer;
return s;
}

std::string format(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
std::string s = format_arg_list(fmt, args);
va_end(args);
return s;
}

15 comments:

snake said...

Very nice piece of code. Saved me some time. Many thanks!

Anonymous said...

Thanks, not exactly how I thought it would be solved. But it works. Thanks.

Perseus said...

cool, but it seems that doest not support UNICODE.

Anonymous said...

std::string s(buffer); delete [] buffer; return s;
}


Won't that give problems? Isn't "s" created on the heap and no longer guaranteed when the function returns (although it will _probably_ be there , just cos it isn't overwritten yet (espeically in non-threaded programs))?

Does your compiler give warnigs? Did you run Lint on it?

Senzee said...

Because the return type is [std::string] and not [std::string &] (or [std::string *]), it is return by value. So, upon return, it will create a copy of the stack variable s in the caller's scope.

So it's completely safe.

Keep in mind however, that this is not an efficient piece of code. It's brute force and requires a number of allocations. Still, it's convenient, and that's often more important than efficiency.

Regarding the earlier comment about not supporting unicode - that's true, but little modification is required for unicode support, such as using std::basic_string<wchar_t> instead of std::string and using the appropriate wide character formatting function.

darkblitz said...

Thanks. What a nice!
In visual Studio 2005, this code meets deprecate warning.
So, I replace _vsnprintf to _vsprintf_s

code :
result = _vsnprintf_s(buffer, length, _TRUNCATE, fmt, args);

spanish coder said...

As you say, it's not the most efficient piece of code, but it is just in turn of a very nice and simple usage, just what I was looking for...

Thank you very much!!

Rick Companje said...

Thanks! This is great!

Anonymous said...

Exactly what I was looking for ...

Thank you !

Steve said...

Thanks a million!!

YuriHan's DreamFactory™ said...

it's gcc ver..

http://blog.yurihan.net/entry/stdstring%EC%9D%98-formating-%EC%8B%9C%EB%B0%9C-%EB%82%9A%EC%98%80%EB%8B%A4

Anonymous said...

It's so great that std::string doesn't have formatting. Just another evidence that c++ designers hadn't coded a single line in ten years.

My expectation level for tr1 is even higher, it so promising that I'll probably change C++ for cobol, or something worse.

Sean said...

If you are using VC, the _vsctprintf function will give you the length need to store the resulting string, and you can get rid of the loop.

Anonymous said...

thanks a lot!

i'd just like to add one function to pass the first length of result to try :)

std::string format_arg_list(DWORD dwdResLenToTryFirst,const char *fmt, va_list args)
{
if(!fmt)return"";
int result=-1;
int length=dwdResLenToTryFirst;
char *buffer=0;
while(result==-1)
{
if(buffer)delete[]buffer;
buffer=new char[length+1];
memset(buffer,0,length+1);
result=_vsnprintf(buffer,length,fmt,args);
length *= 2;
}
std::string s(buffer);
delete[]buffer;
return s;
}

std::string format(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
std::string s=format_arg_list(256,fmt,args);
va_end(args);
return s;
}

std::string formatLen(DWORD dwdResLenToTryFirst,const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
std::string s=format_arg_list(dwdResLenToTryFirst,fmt,args);
va_end(args);
return s;
}

Natalie said...

Thank you so much to Senzee and Sean - your code / comments have helped me solve my evil problem!! There is very little on the web about getting va_list into std::string (or about getting the length of string returned from va_list), so this code is soooooooo helpful - thank you thank you, thank you!!!!