2009/03/25

How to use sprintf/wsprintf with std::string/std::wstring


Explanation


One of the few problems with std::string (and std::wstring) class in C++ is the lack of sprintf function that would return std::string as result. Or sprintf-styled constructor would be nice to have. Of course, there are many alternatives to sprintf - boost format library, sstream class and QString class in Qt 4, but so far plain old sprintf is most compact and easy when compared to them. sstream requires several statements(lines) to make a simple formatted string. Unless you like it too much, it is an overkill for making small formatted string. Boost format library will require boost or a part of it. And Qt 4 QString class will require entire Qt installation, acceptance of one of few available licenses, and it still won't be as compact as sprintf.

Solution



The first thing that comes to mind is to create temporary buffer, sprintf into buffer, and then assign buffer to std::string class. When your programs grows, you'll eventually get sick of many temporary buffers, besides it would still require few lines of code. Here is better way to do that:
Str.h:


#ifndef STR_H
#define STR_H
#include

typedef std::string Str;
typedef std::wstring WStr;

WStr swprintf(const wchar_t* format, ...);
WStr vswprintf(const wchar_t* format, va_list args);
WStr swprintf(const WStr& format, ...);
WStr vswprintf(const WStr& format, va_list args);

Str sprintf(const char* format, ...);
Str vsprintf(const char* format, va_list args);
Str sprintf(const Str& format, ...);
Str vsprintf(const Str& format, va_list args);

#endif




Str.cpp:

#include "Str.h"

WStr swprintf(const wchar_t* format, ...){
va_list args;
va_start(args, format);
WStr result = vswprintf(format, args);
va_end(args);
return result;
}

WStr vswprintf(const wchar_t* format, va_list args){
const int bufferSize = 16384;
wchar_t buffer[bufferSize];
vswprintf(buffer, bufferSize, format, args);
return WStr(buffer);
}

WStr swprintf(const WStr& format, ...){
va_list args;
va_start(args, format);
WStr result = vswprintf(format, args);
va_end(args);
return result;
}

WStr vswprintf(const WStr& format, va_list args){
return vswprintf(format.c_str(), args);
}

Str sprintf(const char* format, ...){
va_list args;
va_start(args, format);
Str result = vsprintf(format, args);
va_end(args);
return result;
}

Str vsprintf(const char* format, va_list args){
const int bufferSize = 16384;
char buffer[bufferSize];
vsnprintf(buffer, bufferSize, format, args);
return Str(buffer);
}

Str sprintf(const Str& format, ...){
va_list args;
va_start(args, format);
Str result = vsprintf(format, args);
va_end(args);
return result;
}

Str vsprintf(const Str& format, va_list args){
return vsprintf(format.c_str(), args);
}





This will allow to quickly create formatted string in one function call.

Problems:



This code works with gcc on Linux system, but it might require some tweaking on different compilers. For example, mingw version of vswprintf has different number of arguments (it doesn't have "buffer size" argument), so it will need to be replaced by another function. In general, wchar_t-based printf functions might cause problems when making cross-platform application. For example, swnprintf exists on MSVC compiler but is missing in gcc. On other hand, default version of vswprintf used in MinGW compiler doesn't have "buffer size" argument, so it is vulnerable to buffer overruns (linux version of function doesn't have this problem).
And yet another problem is that linux/windows versions of wprintf-related functions might handle %s and %S differently. As I remember, in mingw compiler %S in swprintf does the same thing as %s in linux version of printf and vise versa. Those problems can be partially fixed by using few hacks, but people making portable applications with swprintf should be aware of those problems.

Another problem is that there is hard-coded size limit for created strings (it can be changed, but it still doesn't look "nice" when used with C++ string classes. This problem can be bypassed for std::string classes (by using vsnprintf which returns how much characters could not be written in buffer, so you could allocate buffer dynamically, then sprintf into it, and then assign it to std::string), but not for std::wstring (vswnprintf is not available on gcc, doesn't look like standard).

Other solutions


To my opinion, the best (not the fastest) way to make custom sprintf for std::string classes is probably to write it from scratch (in C++ it might be easier) or derive from existing C implementation of sprintf (for example, you can take one out of freebsd source repository). The reason for that is that several printf implementations might have differences. The problem here is that it won't be easy, and you probably will need some time to write standard-compliant (or "format-compliant") version of swprintf/sprintf which will operate on std::string/std::wstring. Of course, you can also implement limited version of those functions.

Another way is to make your own formatting routines or use already mentioned ones: boost format library, QString or sstream. This way you won't get sprintf function, but few formatting routines to use instead.

No comments:

Post a Comment