I have to format std::string
with sprintf
and send it into file stream. How can I do this?

- 44,692
- 7
- 66
- 118

- 61,946
- 78
- 197
- 306
-
1@Ockonal — For the sake of the community (I couldn't care less about my rep) I suggest you change your selection. The one currently selected, in the first snippet, presents a bug waiting to happen in its use of an arbitrary max length. The second snippet completely ignores your stated desire to use vargs like sprintf. I suggest you select the ONLY answer here that is clean, safe, relies only on C++ standards, tested, and well commented. That it is mine is not relevant. It is objectively true. See https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/49812018#49812018. – Douglas Daseeco Sep 21 '18 at 21:31
-
@TrevorBoydSmith a `std::format` was added to C++20 BTW: https://stackoverflow.com/a/57286312/895245 Awesome! – Ciro Santilli OurBigBook.com Jul 31 '19 at 08:24
-
4@CiroSantilli i read an article about `C++20` just yesterday and i saw that `C++20` copied `boost` (for the millionth time now) by adding the `std::format` to the `C++20` spec! I was very very happy! Almost every C++ file I have written in the last 9 years has used `boost::format`. adding official printf style output to streams in C++ will go a long way IMO for all of C++. – Trevor Boyd Smith Jul 31 '19 at 12:13
-
4@TrevorBoydSmith That is not correct. `std::format` is using replacement fields (`{}`) as opposed to the `%`-style of `printf` and `boost::format`. This was initially implemented in the library `{fmt}` by Victor Zverovich, who also also authored C++20's `std::format` and has [an answer in this very thread](https://stackoverflow.com/a/25440014/2533467). – iFreilicht Oct 15 '20 at 10:28
44 Answers
Modern C++ makes this super simple.
C++20
C++20 introduces std::format
, which allows you to do exactly that. It uses replacement fields similar to those in python:
#include <iostream>
#include <format>
int main() {
std::cout << std::format("Hello {}!\n", "world");
}
Code from cppreference.com, CC BY-SA and GFDL
Check out the compiler support page to see if it's available in your standard library implementation.
As of 2023-07-18, partial support is available starting from:
- Visual Studio 2019 16.10, released on 2021-05-25
- Clang 14, feature is tracked here
- GCC 13.1, released on 2023-04-26 though support is officially "experimental".
In all other cases, you can resort to the C++11 solution below, or use the {fmt}
library, which has the same semantics as std::format
.
C++11
With C++11s std::snprintf
, this already became a pretty easy and safe task.
#include <memory>
#include <string>
#include <stdexcept>
template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
auto size = static_cast<size_t>( size_s );
std::unique_ptr<char[]> buf( new char[ size ] );
std::snprintf( buf.get(), size, format.c_str(), args ... );
return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}
The code snippet above is licensed under CC0 1.0.
Line by line explanation:
Aim: Write to a char*
by using std::snprintf
and then convert that to a std::string
.
First, we determine the desired length of the char array using a special condition in snprintf
. From cppreference.com:
Return value
[...] If the resulting string gets truncated due to buf_size limit, function returns the total number of characters (not including the terminating null-byte) which would have been written, if the limit was not imposed.
This means that the desired size is the number of characters plus one, so that the null-terminator will sit after all other characters and that it can be cut off by the string constructor again. This issue was explained by @alexk7 in the comments.
int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1;
snprintf
will return a negative number if an error occurred, so we then check whether the formatting worked as desired. Not doing this could lead to silent errors or the allocation of a huge buffer, as pointed out by @ead in the comments.
if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
Because we know that size_s
can't be negative, we use a static cast to convert it from a signed int
to an unsigned size_t
. This way, even the most pedantic compiler won't complain about the conversions that would otherwise happen on the next lines.
size_t size = static_cast<size_t>( size_s );
Next, we allocate a new character array and assign it to a std::unique_ptr
. This is generally advised, as you won't have to manually delete it again.
Note that this is not a safe way to allocate a unique_ptr
with user-defined types as you can not deallocate the memory if the constructor throws an exception!
std::unique_ptr<char[]> buf( new char[ size ] );
In C++14, you could instead use make_unique
, which is safe for user-defined types.
auto buf = std::make_unique<char[]>( size );
After that, we can of course just use snprintf
for its intended use and write the formatted string to the char[]
.
std::snprintf( buf.get(), size, format.c_str(), args ... );
Finally, we create and return a new std::string
from that, making sure to omit the null-terminator at the end.
return std::string( buf.get(), buf.get() + size - 1 );
You can see an example in action here.
If you also want to use std::string
in the argument list, take a look at this gist.
Additional information for Visual Studio users:
As explained in this answer, Microsoft renamed std::snprintf
to _snprintf
(yes, without std::
). MS further set it as deprecated and advises to use _snprintf_s
instead, however _snprintf_s
won't accept the buffer to be zero or smaller than the formatted output and will not calculate the outputs length if that occurs.
So in order to get rid of the deprecation warnings during compilation, you can insert the following line at the top of the file which contains the use of _snprintf
:
#pragma warning(disable : 4996)
Final thoughts
A lot of answers to this question were written before the time of C++11 and use fixed buffer lengths or vargs. Unless you're stuck with old versions of C++, I wouldn't recommend using those solutions. Ideally, go the C++20 way.
Because the C++11 solution in this answer uses templates, it can generate quite a bit of code if it is used a lot. However, unless you're developing for an environment with very limited space for binaries, this won't be a problem and is still a vast improvement over the other solutions in both clarity and security.
If space efficiency is super important, these two solution with vargs and vsnprintf can be useful. DO NOT USE any solutions with fixed buffer lengths, that is just asking for trouble.

- 13,271
- 9
- 43
- 74
-
2Please emphasise in your answer for the Visual Studio users that the version of VS must be at least 2013. From [this](https://msdn.microsoft.com/en-us/library/2ts7cx93%28v=vs.120%29.aspx) article you can see that it works only with VS2013 version: _If buffer is a null pointer and count is zero, len is returned as the count of characters required to format the output, not including the terminating null. To make a successful call with the same argument and locale parameters, allocate a buffer holding at least len + 1 characters._ – cha Mar 18 '15 at 23:52
-
@cha Did you actually try this? From the [doc of this function for VS12 and earlier](https://msdn.microsoft.com/en-us/library/2ts7cx93(v=vs.110).aspx): *If buffer is a null pointer and count is nonzero, or format is a null pointer, the invalid parameter handler is invoked, as described in Parameter Validation. [...]* As `count` is in fact `0` in my code, this neither contradicts the quote you posted nor the [phrasing on cppreference](http://en.cppreference.com/w/cpp/io/c/fprintf): *[...] If bufsz is zero, nothing is written and buffer may be a null pointer[...]* – iFreilicht Mar 20 '15 at 15:11
-
The thing is: It returns -1. For VS2013 it returns the size. I have actually tested it. It does what it says in the doc, I.e. Returns -1 in VS up to 2012 and returns the length of the formatted string in VS2013 – cha Mar 22 '15 at 10:51
-
What if the `count` is zero and `buffer` is not a null pointer? Will that call return -1 as well? – iFreilicht Mar 24 '15 at 12:56
-
2Why don't you use `std::vector
buf(size);` as buffer and return simply `return &buf[0]` ? – moooeeeep Apr 24 '15 at 08:40 -
3@moooeeeep Multiple reasons. Firstly, the goal here is to return an std::string, not a c-string, so you probably meant `return string(&buf[0], size);` or something similar. Secondly, if you were to return a c-string like that, it would cause undefined behaviour because the vector that holds the values you point to will be invalidated on return. Thirdly, when I started to learn C++, the standard didn't define in what order elements had to be stored inside an `std::vector`, so accessing its storage via a pointer was undefined behaviour. Now it'd work, but I see no benefit in doing it that way. – iFreilicht Apr 25 '15 at 09:48
-
2@iFreilicht A new `std::string` will be constructed from the implicitly converted vector ([copy initialization](http://en.cppreference.com/w/cpp/language/copy_initialization)), which is then returned as a copy, as the function signature suggests. Also, the elements of a `std::vector` are, and were always intended to be, stored [contiguously](http://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/). But I take your point that there might be no benefit in doing so. – moooeeeep Apr 27 '15 at 06:36
-
7I really like this solution, however I think the line `return string(buf.get(), buf.get() + size);` should be `return string(buf.get(), buf.get() + size - 1);` else you get a string with a null character on the end. I found this to be the case on gcc 4.9. – Phil Williams May 21 '15 at 21:50
-
1I like your simple and neat answer, but using the `unique_ptr` is pretty over-killed here (and we have to include `memory.h`). Just allocate an array of character and free it after using works fine. – Anh Tuan Aug 22 '15 at 04:43
-
Do you mind to explain why one would allocate `buf` on the heap after all? Wouldn't `char buf [size];` be sufficient to use in this case? – Ben Aug 26 '15 at 08:28
-
@Ben C++ doesn't allow Variable Length Arrays like C does, refer to [this question](http://stackoverflow.com/questions/1887097/variable-length-arrays-in-c) for further details. – iFreilicht Aug 28 '15 at 13:04
-
2@AnhTuan it's true that is enough, but I want to avoid any possible mistake and don't have a lot of experience with C, so I do it the C++ way. There is no significant performance hit in doing it this way and I find it to be a lot safer. – iFreilicht Aug 28 '15 at 13:07
-
4My compiler complains about this solution (gcc version 5.4.0 20160609 Ubuntu 5.4.0-6ubuntu1~16.04.1): `error: format not a string literal and no format arguments [-Werror=format-security]`. This makes sense as the `format` argument to `snprintf()` is a variable, not a literal, and could result in security issues. More here: http://stackoverflow.com/q/9707569/143504 and http://stackoverflow.com/q/9306175/143504. – Julien-L Jul 20 '16 at 16:20
-
@Julien-L that is true, and I don't really know a good way around this. One very ugly way would be to change the function to accept the format string as a template parameter, but other than that I have no idea. – iFreilicht Jul 20 '16 at 20:23
-
@iFreilicht It's too bad because the template parameter pack makes a lot of sense here. On the other compiler I use, gcc version 4.9.3 20150113 shipped by Linaro for ARM platform, with the same warnings, it doesn't complain. – Julien-L Jul 20 '16 at 21:10
-
1Which std::string constructor does the line `return string( buf.get(), buf.get() + size - 1 );` use? I figure you would use `string (const char* s, size_t n);` and thus don't understand the need for `buf.get()` in the second argument. – Snooze Aug 08 '16 at 19:18
-
1@Snooze It's actually using the range constructor. See (6) here: http://en.cppreference.com/w/cpp/string/basic_string/basic_string – iFreilicht Aug 09 '16 at 10:39
-
Nice solution; I was looking for a library that wouldn't use the stream operator because I had an enum class I wanted treated as int despite the having a operator<< function to print a string representation of the enum value. string_format("%d", e) gave me exactly what I wanted. I tried some other libraries line formatx and fmt and they both ended up printing the string representation of my enum. – Stephan Aug 26 '16 at 13:11
-
4Passing a std::string to %s cause a compile error (_error: cannot pass object of non-trivial type 'std::__cxx11::basic_string
' through variadic function; call will abort at runtime [-Wnon-pod-varargs]_) in clang 3.9.1, but in CL 19 it compiles fine and crashes at runtime instead. Any warning flag I can turn on to have that cought at compile time in cl too ? – Zitrax Feb 01 '17 at 08:46 -
1@Zitrax Not that I know. But you should be able to just use `.c_str()` to avoid the error in the first place. No idea why CL 19 would allow this, that seems like a bug. `snprintf()` doesn't really know how to print `std::strings`, see [this question](http://stackoverflow.com/questions/10865957/c-printf-with-stdstring) for reference. – iFreilicht Feb 01 '17 at 10:46
-
@iFreilicht Apparently gcc also compiles it ( See [godbolt comparison](https://godbolt.org/g/qGjRLY) ). I know I can just use `.c_str()`, the problem is that it can be easy to forget when it compiles without it - and I would rather catch the issue as early as possible if there is a way to do it. – Zitrax Feb 01 '17 at 10:53
-
@Zitrax That's quite interesting. I'm sure there's some template metaprogramming magic that could solve this, but I don't know how to approach it of the top of my head. – iFreilicht Feb 01 '17 at 11:02
-
3The compiler will generate a template specialization for every tuple of parameter types. If this function is used many times with different parameters, then a lot of code will be generated and compiled. (code bloat) – ManuelAtWork Jun 07 '17 at 14:53
-
@ManuelAtWork Good point, that is a valid reason to consider a solution with va_args over this one. – iFreilicht Jun 08 '17 at 09:33
-
The way how to get the size doesn't work for me (on Ubuntu and Android native). Supplying a hard coded number will work. Also, I believe your method creates overhead of constructing the string one more time than required. – Joe Nov 22 '17 at 20:06
-
1Earlier in this thread I mentioned the problem of passing std::string directly to %s. I have a solution that makes possible in the following gist: https://gist.github.com/Zitrax/a2e0040d301bf4b8ef8101c0b1e3f1d5 – Zitrax Jan 03 '18 at 19:53
-
@Zitrax I added it to the answer, that's probably going to be very useful. – iFreilicht Jan 04 '18 at 21:52
-
I'm confused how we can pass a parameter pack `args ...` into `snprintf` which takes c-style variadic arguments. Aren't they two different things? – Valentin Feb 24 '18 at 00:16
-
1@Valentin yes they are, but they are compatible this way around. `args ...` is a [parameter pack expansion](http://en.cppreference.com/w/cpp/language/parameter_pack) in this context. Basically, we pass all arguments inside the parameter pack as individual arguments, not the pack as one argument. These are then handled by the `va_args` mechanism inside `snprintf` just like you'd expect. It doesn't know that it receives arguments from a parameter pack, it only sees the individual arguments. – iFreilicht Feb 27 '18 at 09:38
-
I like this. I would add that it can throw an exception if there is an encoding error where snprintf would return < 0. Since it wouldn't make sense to handle this anywhere else in the call stack I added a check for a negative return value and then return the format string if there was an encoding error. This way you can at the very least search the source using the format string to figure out where the error is occuring and fix it. – Steven Eckhoff Oct 05 '18 at 21:05
-
@Steven Eckhoff I tried to find out when this could actually happen, and it seems to be very hard to trigger an encoding error, but I agree it's better to fail silently here than to just let an exception be thrown. You wanna suggest an edit? – iFreilicht Oct 07 '18 at 06:24
-
From what I understand it is very hard to make it happen. I look into ways to do it. So I am using your solution quite happily, but I added a check on size before adding 1 to it. If I get a negative return value I just return the format string. This allows someone who notices the error to search source for the format string. I would provide code, but I cannot do it in a comment. – Steven Eckhoff Oct 09 '18 at 01:29
-
2A small problem with this useful solution: snprintf returns an int (negative for errors) - adding 1 and casting to size_t "swallows" possible problems or even could lead to huge memory being allocated (if -2 is returned for example). – ead Nov 26 '19 at 08:14
-
@ead good catch! How should the function behave in that case? It has to return some valid string as well, should it just return the unformatted string? Could that be a security hazard in some way? Maybe it could thrown an exception, but that doesn't seem like a terribly good idea either. – iFreilicht Nov 26 '19 at 19:44
-
1One option is to throw, another is to change singnature to bool string_format(std::string& output, const char* format, ...) or return an std::optinal instead of string or similar. In a library one would probably provide both versions, because depending on situation the first or the second options would make more sense. – ead Nov 26 '19 at 20:05
-
@ead i opted for throwing. The function should be easy to use and formatting errors are quite rare from my experience. Reporting errors in return codes is not considered good style anymore either. – iFreilicht Dec 16 '19 at 12:21
-
std::format() certainly sounds like the the way to go, into the future. Too bad there isn't a compiler that's implemented it yet! It's not in my gcc (9.3) and according to [this](https://en.cppreference.com/w/cpp/compiler_support/20), it's not anywhere yet... – moodboom Nov 06 '20 at 00:39
-
1
-
A revision changed the C++11-specific example to use `make_unique`, which is not immediately available in C++11. Can we change this back and, if desired, make a C++14 section which utilizes `make_unique`? – jdknight Mar 02 '22 at 20:52
-
1@jdknight Good point! I made the solution C++11 compatible again. Using `make_unique` is merely a style choice in this specific scenario, but I added it to the line-by-line explanation. This should be a good compromise :) – iFreilicht Mar 03 '22 at 16:54
-
`size_s` should just have the `snprintf()` return value and the following check should be `<0`. That makes the check match the canonical safety check `<0` for this function, which I think is a plus. I'd then rename the variable `len` as that's what `snprintf()` returns. The `+1` should appear in the parameter for new, so you don't need the `-1` at the end. – – Swiss Frank Oct 28 '22 at 10:21
-
The best errors to throw, if your audience is technical, is the exact function call and return value so they can look it up. You can at least say `"snprintf()<0"`. `"Error during formatting"` doesn't help a technical OR untechnical audience have any idea what's happening. Finally, `"snprintf()<0"` will be accessible to a non-English programmer. – Swiss Frank Oct 28 '22 at 11:03
You can't do it directly, because you don't have write access to the underlying buffer (until C++11; see Dietrich Epp's comment). You'll have to do it first in a c-string, then copy it into a std::string:
char buff[100];
snprintf(buff, sizeof(buff), "%s", "Hello");
std::string buffAsStdStr = buff;
But I'm not sure why you wouldn't just use a string stream? I'm assuming you have specific reasons to not just do this:
std::ostringstream stringStream;
stringStream << "Hello";
std::string copyOfStr = stringStream.str();
-
20The magic cookie in `char buf[100];` makes this solution not very robust. But the essential idea is there. – John Dibling Feb 26 '10 at 14:40
-
20John,streams are not slow. The only reason streams seem slow is that by default the iostreams are synchronizing with C FILE output so that intermixed cout and printfs are output correctly. Disabling this link (with a call to cout.sync_with_stdio(false)) causes c++'s streams to outperform stdio, at least as of MSVC10. – Jimbo Jan 20 '13 at 21:15
-
80The reason to use formats is to let a localizer rebuild the structure of the sentence for foreign languages, instead of hard coding the grammar of the sentence. – Martijn Courteaux Jul 09 '13 at 14:51
-
278For some reason, other languages use printf-like syntax: Java, Python (the new syntax is still closer to printf than to streams). Only C++ inflicts this verbose abomination on innocent human beings. – quant_dev Apr 05 '15 at 00:29
-
13Even better, use `asprintf`, which allocates a new string with enough space to hold the result. Then copy that to an `std::string` if you like, and remember to `free` the original. Also, it's possible to put this in a macro so that any good compiler will help validate the format for you - you don't want to put a `double` where a `%s` is expected – Aaron McDaid Jul 10 '15 at 14:35
-
"You don't have write access to the buffer..." This is just incorrect. – Dietrich Epp Jan 25 '16 at 21:02
-
1@DietrichEpp No, it's correct. While you can obtain a `const char*` pointer and `const_cast` it, the standard explicitly states that you are *not allowed* to modify it. See [\[string.accessors\]](http://eel.is/c++draft/string.accessors): "The program shall not alter any of the values stored in the character array." ...unless there's something I'm missing? – villapx Apr 21 '16 at 19:38
-
5@villapx: There is something you are missing. See this answer: http://stackoverflow.com/questions/25169915/is-writing-to-str0-buffer-of-a-stdstring-well-defined-behaviour-in-c11 – Dietrich Epp Apr 21 '16 at 19:45
-
@DietrichEpp Ah. I'm not too familiar with C++11 admittedly, as we still pretty much only use C++98/03 here at work. I was indeed missing something :) – villapx Apr 21 '16 at 19:46
-
1Write your own variadic function to take in printf like formatting options. – Owl Aug 16 '16 at 08:49
-
2to use osstream add `#include
` at the beginning of your program. – Markus Weber Sep 13 '16 at 09:24 -
5
-
2`But I'm not sure why you wouldn't just use a string stream...` code bloat at call site? It's not just code bloat it's [insane code bloat](https://godbolt.org/g/HnvnEi). And here's how [printf version looks like](https://godbolt.org/g/wuH485) – Pavel P Feb 19 '18 at 09:11
-
I did mine own investigation here and gain less performance results versus plain `snprints`: https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/52766252#52766252 – Andry Oct 11 '18 at 18:47
-
This solution does not handle anything but the most unlikely cases and would make a poorly designed function. When the buf_siz of any of the vprintf family of functions is zero, nothing is either written or dynamically allocated, however the number of bytes that would be written (not including the null terminator) is still calculated and returned. That was by design so that an efficient and safe solution can be written for questions such as this. A production quality solution is here: https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/49812018#49812018 – Douglas Daseeco Oct 14 '18 at 08:20
-
2Why is there a culture of diminishing the importance of terse semantics? A stringstream is a very painful object to use -- literally. If you have a job where you write code non-stop all day, every day, C++ gets to be a very painful code to write. As in: it hurts your hands. – Chris Aug 14 '20 at 13:10
-
1@quant_dev The irony is C++ was written to make life easier for the programmer and an onus was put on the compiler to make things happen. The main critique of `sprintf` format specifiers is type safety. However, today (2020) compilers have format specifier checking built-in to check argument matching which doesn't work in all cases, but handles a majority. Ie. 'C' has put the smarts in the tools. C++20 adds `std::format` to heap more irony on past arguments. – artless noise Dec 18 '20 at 15:20
-
An ostringstream object can be used to write to a string. This is similar to the C sprintf() function. For example: ostringstream s1; int x = 22; s1 << "Hello worlds " << x << endl; string s2 = s1.str(); cout << s2; for reusability call s1.str(""); s1 <<"Hello new world "<
– Milind Morey Mar 16 '21 at 11:28 -
he doesnt want to use string streams because they are bloated AF, have you thought why all modern languages have a string formatting functionality ? – Apr 08 '21 at 12:59
C++11 solution that uses vsnprintf()
internally:
#include <stdarg.h> // For va_start, etc.
std::string string_format(const std::string fmt, ...) {
int size = ((int)fmt.size()) * 2 + 50; // Use a rubric appropriate for your code
std::string str;
va_list ap;
while (1) { // Maximum two passes on a POSIX system...
str.resize(size);
va_start(ap, fmt);
int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
va_end(ap);
if (n > -1 && n < size) { // Everything worked
str.resize(n);
return str;
}
if (n > -1) // Needed size returned
size = n + 1; // For null char
else
size *= 2; // Guess at a larger size (OS specific)
}
return str;
}
A safer and more efficient (I tested it, and it is faster) approach:
#include <stdarg.h> // For va_start, etc.
#include <memory> // For std::unique_ptr
std::string string_format(const std::string fmt_str, ...) {
int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
std::unique_ptr<char[]> formatted;
va_list ap;
while(1) {
formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
strcpy(&formatted[0], fmt_str.c_str());
va_start(ap, fmt_str);
final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
va_end(ap);
if (final_n < 0 || final_n >= n)
n += abs(final_n - n + 1);
else
break;
}
return std::string(formatted.get());
}
The fmt_str
is passed by value to conform with the requirements of va_start
.
NOTE: The "safer" and "faster" version doesn't work on some systems. Hence both are still listed. Also, "faster" depends entirely on the preallocation step being correct, otherwise the strcpy
renders it slower.

- 9,137
- 11
- 75
- 83

- 11,620
- 5
- 64
- 44
-
3slow. why increase size by 1? And when does this funciton return -1? – 0xDEAD BEEF May 18 '12 at 10:45
-
2@dead beef: origin of this code is from the documentation of vsnprintf ( http://www.tin.org/bin/man.cgi?section=3&topic=vsnprintf ), there in the comments your questions are answered. – Janosch Jun 05 '12 at 12:32
-
28
-
1@xiaomao: as a general rule, yes it is. However, the risk is mitigated by the second parameter of `vsnprintf` which sets a limit on the number of characters to write. – John McFarlane Aug 24 '12 at 22:17
-
8va_start with a reference argument has problems on MSVC. It fails silently and returns pointers to random memory. As a workaround, use std::string fmt instead of std::string &fmt, or write a wrapper object. – Steve Hanov Aug 27 '12 at 17:53
-
6I +1'd cause I know this will probably work based on how most std::strings are implemented, however c_str isn't really intended to be a place to modify the underlying string. Its supposed to be read-only. – Doug T. Sep 25 '12 at 14:57
-
Please update your answer for MSVC. The first line must be changed to:std::string string_format(const std::string fmt, ...), as the reference will not allow vsnprintf to work. – Dan Feb 09 '13 at 23:57
-
1for most questions, see dead_beef's comment. For MSVC, I edited the response to remove the reference, since a) compliance with standards and b) passing const strings is supposed to be very efficient anyway – Erik Aronesty Mar 19 '13 at 18:16
-
3For reference arguments, it's not MSVC problem. It's required by the standard: http://www.cplusplus.com/reference/cstdarg/va_start/ (See requirements for `paramN` in C++.) – Masood Khaari May 15 '13 at 08:57
-
6And to obtain the resulting string length beforehand, see: http://stackoverflow.com/a/7825892/908336 I don't see the point in increasing `size` in each iteration, when you can obtain it by the first call of `vsnprintf()`. – Masood Khaari May 15 '13 at 09:12
-
1But beware, because MSVC _vsnprintf acts differently from vsnprintf when it comes to when and how it returns the size. – Paul Du Bois Sep 11 '13 at 00:11
-
Won't returning the string object that was created on the stack be invalid? – samoz Oct 26 '13 at 12:59
-
1@miki725 Why `&formatted[0]` instead of `formatted.get()`? (Also my answer uses `std::vector
` instead of `std::unique_ptr – ChetS Nov 28 '13 at 00:09` with the same goal of _safe_ code but I am unsure about _efficient_.) -
-
The "safer and better" version doesn't compile for me: error: "unique_ptr" is not a member of "std" – Erik Aronesty Jul 03 '14 at 20:17
-
2@MassoodKhaari There is no real iteration... it only happens once on O/S'es that adhere to POSIX standard. Guessing up front, correctly, improves performance 2-fold. If you test with and without the guessing... you'll see that all the performance relies upon the accuracy of the guess. – Erik Aronesty Aug 27 '14 at 15:12
-
@PaulDuBois That's precisely why the first version exists. To deal with O/S specific weirdness. – Erik Aronesty Aug 27 '14 at 15:13
-
2For those who are concerned with using strings as writable buffers, please see: http://stackoverflow.com/questions/25169915/is-writing-to-str0-buffer-of-a-stdstring-well-defined-behaviour-in-c11 – Erik Aronesty Jun 03 '16 at 16:33
-
3@MassoodKhaari It's twice as slow to have vsnprintf return the size first. And it's unreliable on some operating systems. Better to make a good guess, and then, if vsnprintf fails, double it. This leads to the O(1) algo because of the exponent. most of the time, you won't do more than 1 pass. – Erik Aronesty Jun 03 '16 at 16:36
-
1Neither code snippet is particularly safe or simple, and neither is any faster than the production quality answer below (https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/49812018#49812018). – Douglas Daseeco Jun 16 '18 at 08:47
-
@quantum it is save according to the specifications, yes. memory is guaranteed to be allocated contiguously. – Erik Aronesty Sep 21 '18 at 13:13
-
Using a "int size = ((int)fmt.size()) * 2 + 50; // Use a rubric appropriate for your code" is not only safe, it is arbitrary and absurd. – Douglas Daseeco Sep 21 '18 at 21:08
-
Using a loop may be safe, but it is not efficient for many cases, the cases you probably did not test. The correct implementation is clearly indicated by the FACT that, if the buf_siz of any of the vprintf family of functions is zero, nothing is written and buffer may be a null pointer, however the return value (number of bytes that would be written not including the null terminator) is still calculated and returned. The correct answer is the one you down voted: https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/49812018#49812018 – Douglas Daseeco Sep 21 '18 at 21:13
-
However, when performance testing the "correct answer" is slow and uses twice the number of memory allocations. – Erik Aronesty Sep 27 '18 at 16:20
-
I did mine own investigation here and gain diametrically opposite results: https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/52766252#52766252 – Andry Oct 11 '18 at 18:49
-
-
C++20 has std::format
which resembles sprintf
in terms of API but is fully type-safe, works with user-defined types, and uses Python-like format string syntax. Here's how you can format std::string
and write it to a stream:
std::cout << std::format("The answer is {}.", 42);
Alternatively, you could use the {fmt} library to format a string and write it to stdout
or a file stream in one go:
fmt::print("The answer is {}.", 42);
As for sprintf
or most of the other answers here, unfortunately they use varargs and are inherently unsafe unless you use something like GCC's format
attribute which only works with literal format strings. You can see why these functions are unsafe on the following example:
std::string format_str = "%s";
string_format(format_str, format_str[0]);
where string_format
is an implementation from the Erik Aronesty's answer. This code compiles, but it will most likely crash when you try to run it:
$ g++ -Wall -Wextra -pedantic test.cc
$ ./a.out
Segmentation fault: 11
Disclaimer: I'm the author of {fmt} and C++20 std::format
.

- 49,672
- 25
- 199
- 336
-
-
1This is just a snippet, not a complete code. Obviously you need to include
and put the code in a function. – vitaut Feb 20 '18 at 08:20 -
for me is not so obvious , IMHO you should include it in snippet , thanks for the feedback – Sérgio Feb 20 '18 at 18:14
-
Building off of Erik Aronesty's answer is a red herring. His first code sample is unsafe and his second is inefficient and clumsy. The clean implementation is clearly indicated by the fact that, if the buf_siz of any of the vprintf family of functions is zero, nothing is written and buffer may be a null pointer, however the return value (number of bytes that would be written not including the null terminator) is still calculated and returned. A production quality answer is here: https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/49812018#49812018 – Douglas Daseeco Sep 21 '18 at 21:16
-
1An `fmt` like implementation was added to C++20! https://stackoverflow.com/a/57286312/895245 fmt currently claims support for it. Awesome work! – Ciro Santilli OurBigBook.com Jul 31 '19 at 08:25
boost::format()
provides the functionality you want:
As from the Boost format libraries synopsis:
A format object is constructed from a format-string, and is then given arguments through repeated calls to operator%. Each of those arguments are then converted to strings, who are in turn combined into one string, according to the format-string.
#include <boost/format.hpp>
cout << boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50;
// prints "writing toto, x=40.230 : 50-th try"

- 20,795
- 11
- 69
- 104

- 510,854
- 105
- 1,084
- 1,005
-
5you can prune the libraries you need out of boost as well. Using a suplied tool. – Hassan Syed Feb 26 '10 at 16:23
-
10Boost Format is not only big, but also very slow. See http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html and http://www.boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/performance_measurements/numeric_performance/double_performance.html – vitaut May 20 '14 at 01:43
-
19Including boost anywhere in your project immediately increases significantly compile times. For large projects, it most probably doesn't matter. For small projects, boost is a drag. – quant_dev Apr 05 '15 at 00:30
-
3@vitaut While it is *terribly* resource consuming when compared to the alternatives. How often do you format strings? Considering it only takes a few micro seconds and most projects probably only use it a few dozen times, it is not noticeable in a project that doesn't focus heavily on string formatting, right? – AturSams Aug 17 '15 at 12:04
-
2Unfortunatelly, boost::format does not work the same way: does not accept the var_args. Some people like to have all code related to a single program looking the same/using the same idioms. – xor007 Oct 26 '15 at 14:37
Tested, Production Quality Answer
This answer handles the general case with standards compliant techniques. The same approach is given as an example on CppReference.com near the bottom of their page. Unlike their example, this code fits the question's requirements and is field tested in robotics and satellite applications. It also has improved commenting. Design quality is discussed further below.
#include <string>
#include <cstdarg>
#include <vector>
// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {
// initialize use of the variable argument array
va_list vaArgs;
va_start(vaArgs, zcFormat);
// reliably acquire the size
// from a copy of the variable argument array
// and a functionally reliable call to mock the formatting
va_list vaArgsCopy;
va_copy(vaArgsCopy, vaArgs);
const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
va_end(vaArgsCopy);
// 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(), iLen); }
#include <ctime>
#include <iostream>
#include <iomanip>
// demonstration of use
int main() {
std::time_t t = std::time(nullptr);
std::cerr
<< std::put_time(std::localtime(& t), "%D %T")
<< " [debug]: "
<< vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
<< std::endl;
return 0; }
Predictable Linear Efficiency
Two passes are necessities for a secure, reliable, and predictable reusable function per the question specifications. Presumptions about the distribution of sizes of vargs in a reusable function is bad programming style and should be avoided. In this case, arbitrarily large variable length representations of vargs is a key factor in choice of algorithm.
Retrying upon overflow is exponentially inefficient, which is another reason discussed when the C++11 standards committee discussed the above proposal to provide a dry run when the write buffer is null.
In the above production ready implementation, the first run is such a dry run to determine allocation size. No allocation occurs. Parsing of printf directives and the reading of vargs has been made extremely efficient over decades. Reusable code should be predictable, even if a small inefficiency for trivial cases must be sacrificed.
Security and Reliability
Andrew Koenig said to a small group of us after his lecture at a Cambridge event, "User functions shouldn't rely on the exploitation of a failure for unexceptional functionality." As usual, his wisdom has been shown true in the record since. Fixed and closed security bug issues often indicate retry hacks in the description of the hole exploited prior to the fix.
This is mentioned in the formal standards revision proposal for the null buffer feature in Alternative to sprintf, C9X Revision Proposal, ISO IEC Document WG14 N645/X3J11 96-008. An arbitrarily long string inserted per print directive, "%s," within the constraints of dynamic memory availability, is not an exception, and should not be exploited to produce, "Unexceptional functionality."
Consider the proposal along side the example code given at the bottom of the C++Reference.org page linked to in the first paragraph of this answer.
Also, the testing of failure cases is rarely as robust of success cases.
Portability
All major O.S. vendors provide compilers that fully support std::vsnprintf as part of the c++11 standards. Hosts running products of vendors that no longer maintain distributions should be furnished with g++ or clang++ for many reasons.
Stack Use
Stack use in the 1st call to std::vsnprintf will be less than or equal to that of the 2nd, and and it will be freed before the 2nd call begins. If the first call exceeds stack availability, then std::fprintf would fail too.

- 3,475
- 21
- 27
-
Brief and robust. It could fail on HP-UX, IRIX, Tru64 which have have non-conforming vsnprintf-s. EDIT: also, considering how two-passes might impact performances, esp. for most common, small strings formatting, have you considered a guess for the initial pass, that might be sufficiently large? – Engineerist Dec 22 '18 at 15:46
-
2FWIW, the guessing I was referring to uses a stack-allocated buffer where the first run occurs. If it fits it saves the cost of a second run and the dynamic allocation that occurs there. Presumably, small strings are more frequently used than large strings. In my crude benchmark that strategy (almost) halves the running time for small strings and is within a few percents (fixed overhead maybe?) of the strategy above. Would you please elaborate on the C++11 design that employs a dry run, etc.? I would like to read about it. – Engineerist Dec 23 '18 at 14:06
-
@Engineerist, your questions have been addressed in the body of the answer, above and below the code. The sub topics can be made easier to read that way. – Douglas Daseeco Dec 23 '18 at 23:34
-
2
-
2
I wrote my own using vsnprintf so it returns string instead of having to create my own buffer.
#include <string>
#include <cstdarg>
//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
int size = 512;
char* buffer = 0;
buffer = new char[size];
va_list vl;
va_start(vl, fmt);
int nsize = vsnprintf(buffer, size, fmt, vl);
if(size<=nsize){ //fail delete buffer and try again
delete[] buffer;
buffer = 0;
buffer = new char[nsize+1]; //+1 for /0
nsize = vsnprintf(buffer, size, fmt, vl);
}
std::string ret(buffer);
va_end(vl);
delete[] buffer;
return ret;
}
So you can use it like
std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

- 4,147
- 3
- 33
- 50

- 2,110
- 21
- 20
-
This does a full extra copy of the data, it's possible to use `vsnprintf` directly into the string. – Mooing Duck Mar 12 '13 at 16:47
-
1Use the code in http://stackoverflow.com/a/7825892/908336 to obtain the resulting string length beforehand. And you can use smart pointers for an exception-safe code: `std::unique_ptr
buffer (new char[size]);` – Masood Khaari May 15 '13 at 09:19 -
2I'm not sure this is correct in the fallback case; I think you need to do a va_copy of vl for the second vsnprintf() to see the arguments correctly. For an example see: https://github.com/haberman/upb/blob/26d98ca94f2f049e8767b4a9a33d185a3d7ea0fd/upb/upb.c#L17 – Josh Haberman Nov 02 '13 at 18:25
-
You cannot reuse vl without calling va_end() and va_start() again. Add a va_end() after both vsnprintf's and and add a va_start before the second one. – Swiss Frank Oct 28 '22 at 22:40
C++20 std::format
It has arrived! The feature is described at: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html and uses a Python-like .format()
syntax.
I expect that the usage will be like:
#include <format>
#include <string>
int main() {
std::string message = std::format("The answer is {}.", 42);
}
GCC 9.1.0 with g++-9 -std=c++2a
still doesn't support it.
The existing fmt
library implements it for before it gets official support: https://github.com/fmtlib/fmt as previously mentioned at: std::string formatting like sprintf Install on Ubuntu 22.04:
sudo apt install libfmt-dev
Modify source to replace:
<format>
with<fmt/core.h>
std::format
tofmt::format
main.cpp
#include <string>
#include <iostream>
#include <fmt/core.h>
int main() {
std::string message = fmt::format("The answer is {}.", 42);
std::cout << message << std::endl;
}
and compile and run with:
g++ -std=c++11 -o main.out main.cpp -lfmt
./main.out
Output:
The answer is 42.
The API will add a new std::format
header:
The proposed formatting API is defined in the new header
<format>
and should have no impact on existing code.
Hexadecimal format {:x}
Leading zeroes {:03}
Print leading zeros with C++ output operator?
Alignment left {:<}
, right {:>}
, center {:^}
C++ alignment when printing cout <<
Floating point precision {:.2}
- How do I print a double value with full precision using cout?
- Set back default floating point print precision in C++
Show sign on positive numbers {:+}
How to print positive numbers with a prefix + in C++
Show booleans as true
and false
: {:}

- 347,512
- 102
- 1,199
- 985
In order to format std::string
in a 'sprintf' manner, call snprintf
(arguments nullptr
and 0
) to get length of buffer needed. Write your function using C++11 variadic template like this:
#include <cstdio>
#include <string>
#include <cassert>
template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
int length = std::snprintf( nullptr, 0, format, args... );
assert( length >= 0 );
char* buf = new char[length + 1];
std::snprintf( buf, length + 1, format, args... );
std::string str( buf );
delete[] buf;
return str;
}
Compile with C++11 support, for example in GCC: g++ -std=c++11
Usage:
std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);

- 6,060
- 3
- 32
- 53
-
std::snprintf is not available in VC++12 (Visual Studio 2013). Replace it with _snprintf instead. – Shital Shah Apr 12 '16 at 06:31
-
why you do not use ```char buf[length + 1];``` instead of ```char* buf = new char[length + 1];``` ? – Behrouz.M May 19 '16 at 06:49
-
1The difference between using `char[]` and `char*` with new, is that in the former case buf would be allocated on stack. It is OK for small buffers, but since we cannot guarantee size of resulting string, it is slightly better to use `new`. For example on my machine `string_sprintf("value: %020000000d",5)` , print outrageous number of leading zeros before number 5, core dumps when using array on stack, but works OK when using dynamically allocated array `new char[length + 1]` – user2622016 May 19 '16 at 13:52
-
very clever idea to get the actual buff size needed for formatted output – Eddie Deng Sep 03 '16 at 13:11
-
@raypixar because VLAs are a C99 feature unsupported in standard C++ (and it even became optional starting from C11). – Ruslan Jul 09 '17 at 15:33
-
1@user2622016: Thanks for the solution! Please note that `std::move` [is superfluous](https://stackoverflow.com/a/14856553/1174378). – Mihai Todor Jun 18 '19 at 15:45
-
This unfortunately doesn't work with GCC's `-Werror=format-security`. – Matthias Urlichs Feb 01 '20 at 14:41
-
@MatthiasUrlichs You mean -Werror=format-nonliteral? It does work with `-Wformat -Werror=format-security`, but does not with `-Wformat -Werror=format-nonliteral` or `-Wformat=2` (checked on gcc and clang), which is expected since these errors are non-standard C++ extensions. – user2622016 Feb 01 '20 at 22:18
If you only want a printf-like syntax (without calling printf yourself), have a look at Boost Format.

- 24,095
- 5
- 52
- 70
-
Adding an entire library for such a simple thing is not nessecary. This was answered at https://stackoverflow.com/questions/19009094/c-variable-arguments-with-stdstring-only/49812356#49812356. – Douglas Daseeco Jul 24 '18 at 09:55
[edit: 20/05/25] better still...:
In header:
// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>
void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }
The PRINTSTRING(r)
-function is to cater for GUI or terminal or any special output needs using #ifdef _some_flag_
, the default is:
void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }
[edit '17/8/31] Adding a variadic templated version 'vtspf(..)':
template<typename T> const std::string type_to_string(const T &v)
{
std::ostringstream ss;
ss << v;
return ss.str();
};
template<typename T> const T string_to_type(const std::string &str)
{
std::istringstream ss(str);
T ret;
ss >> ret;
return ret;
};
template<typename...P> void vtspf_priv(std::string &s) {}
template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
s+=type_to_string(h);
vtspf_priv(s, p...);
}
template<typename...P> std::string temp_vtspf(P...p)
{
std::string s("");
vtspf_priv(s, p...);
return s;
}
which is effectively a comma-delimited version (instead) of the sometimes hindering <<
-operators, used like this:
char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);
[edit] Adapted to make use of the technique in Erik Aronesty's answer (above):
#include <string>
#include <cstdarg>
#include <cstdio>
//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
int n, size=100;
bool b=false;
va_list marker;
while (!b)
{
s.resize(size);
va_start(marker, fmt);
n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
va_end(marker);
if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
}
}
//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
std::string ss;
int n, size=100;
bool b=false;
va_list marker;
while (!b)
{
ss.resize(size);
va_start(marker, fmt);
n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
va_end(marker);
if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
}
s += ss;
}
[previous answer]
A very late answer, but for those who, like me, do like the 'sprintf'-way: I've written and are using the following functions. If you like it, you can expand the %-options to more closely fit the sprintf ones; the ones in there currently are sufficient for my needs.
You use stringf() and stringfappend() same as you would sprintf. Just remember that the parameters for ... must be POD types.
//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
char *s, ch=0;
int n, i=0, m;
long l;
double d;
std::string sf = sformat;
std::stringstream ss;
m = sf.length();
while (i<m)
{
ch = sf.at(i);
if (ch == '%')
{
i++;
if (i<m)
{
ch = sf.at(i);
switch(ch)
{
case 's': { s = va_arg(marker, char*); ss << s; } break;
case 'c': { n = va_arg(marker, int); ss << (char)n; } break;
case 'd': { n = va_arg(marker, int); ss << (int)n; } break;
case 'l': { l = va_arg(marker, long); ss << (long)l; } break;
case 'f': { d = va_arg(marker, double); ss << (float)d; } break;
case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
case 'X':
case 'x':
{
if (++i<m)
{
ss << std::hex << std::setiosflags (std::ios_base::showbase);
if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
char ch2 = sf.at(i);
if (ch2 == 'c') { n = va_arg(marker, int); ss << std::hex << (char)n; }
else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
else if (ch2 == 'l') { l = va_arg(marker, long); ss << std::hex << (long)l; }
else ss << '%' << ch << ch2;
ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
}
} break;
case '%': { ss << '%'; } break;
default:
{
ss << "%" << ch;
//i = m; //get out of loop
}
}
}
}
else ss << ch;
i++;
}
va_end(marker);
sF = ss.str();
}
//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
va_list marker;
va_start(marker, sformat);
DoFormatting(stgt, sformat, marker);
}
//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
string sF = "";
va_list marker;
va_start(marker, sformat);
DoFormatting(sF, sformat, marker);
stgt += sF;
}

- 7,069
- 9
- 54
- 80
-
@MooingDuck: Changed function parameter as per Dan's comment to Aronesty's answer. I use only Linux/gcc, and with `fmt` as reference it works fine. (But I suppose people will want to play with toys, so ...) If there are any other supposed 'bugs' could you please elaborate? – slashmais Mar 12 '13 at 13:21
-
I misunderstood how part of his code worked and thought it was doing to many resizes. Reexamining shows that I was mistaken. Your code is correct. – Mooing Duck Mar 12 '13 at 16:45
-
Building off of Erik Aronesty's answer is a red herring. His first code sample is unsafe and his second is inefficient and clumsy. The clean implementation is clearly indicated by the fact that, if the buf_siz of any of the vprintf family of functions is zero, nothing is written and buffer may be a null pointer, however the return value (number of bytes that would be written not including the null terminator) is still calculated and returned. A production quality answer is here: https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/49812018#49812018 – Douglas Daseeco Sep 21 '18 at 21:19
-
template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
size_t size = snprintf(nullptr, 0, fmt, args...);
std::string buf;
buf.reserve(size + 1);
buf.resize(size);
snprintf(&buf[0], size + 1, fmt, args...);
return buf;
}
Using C99 snprintf and C++11

- 316
- 4
- 7
-
Of all the answers, this one is has the two things you want: Using C++ type safety (no `va_list`) and it writes directly into the `std::string` without a temporary buffer. I posted a Gist with a header library that does exactly this, but I'll only add it to this answer because it's essentially the same idea (except it also supports `std::wstring`). https://gist.github.com/dwcullop/aabb2007ba0dcd8c7e80a761545e6ca3 – Darrin Cullop Aug 13 '21 at 17:57
-
It also correctly allocates a null terminator but ensures that the '\0' is *NOT* included in the size, creating a result that works properly with both code that is size-aware and code that expects null termination. This is a subtle but important detail that many other answers have overlooked. – Darrin Cullop Aug 13 '21 at 18:04
This is how google does it: StringPrintf
(BSD License)
and facebook does it in a quite similar fashion: StringPrintf
(Apache License)
Both provide with a convenient StringAppendF
too.

- 3,727
- 32
- 35
My two cents on this very popular question.
To quote the manpage of printf
-like functions:
Upon successful return, these functions return the number of characters printed (excluding the null byte used to end output to strings).
The functions snprintf() and vsnprintf() do not write more than size bytes (including the terminating null byte ('\0')). If the output was truncated due to this limit then the return value is the number of characters (excluding the terminating null byte) which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated.
In other words, a sane C++11 implementation should be the following:
#include <string>
#include <cstdio>
template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
char b;
size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
// See comments: the +1 is necessary, while the first parameter
// can also be set to nullptr
char bytes[required];
std::snprintf(bytes, required, fmt.c_str(), vs...);
return std::string(bytes);
}
It works quite well :)
Variadic templates are supported only in C++11. The answer from pixelpoint show a similar technique using older programming styles.
It's weird that C++ does not have such a thing out of the box. They recently added to_string()
, which in my opinion is a great step forward. I'm wondering if they will add a .format
operator to the std::string
eventually...
Edit
As alexk7 pointed out, A +1
is needed on the return value of std::snprintf
, since we need to have space for the \0
byte. Intuitively, on most architectures missing the +1
will cause the required
integer to be partially overwritten with a 0
. This will happen after the evaluation of required
as actual parameter for std::snprintf
, so the effect should not be visible.
This problem could however change, for instance with compiler optimization: what if the compiler decides to use a register for the required
variable? This is the kind of errors which sometimes result in security issues.

- 13,590
- 11
- 60
- 87
-
1snprintf always appends a terminating null-byte but returns the number of characters without it. Doesn't this code always skip the last character? – alexk7 Jan 04 '15 at 19:12
-
@alexk7, Nice catch! I'm updating the answer. The code does not skip the last character, but writes beyond the end of the `bytes` buffer, probably over the `required` integer (which fortunately at that point is already evaluated). – Dacav Jan 05 '15 at 07:12
-
1Just a small hint: With a buffer size of 0, you can pass a `nullptr` as the buffer argument, eliminating the `char b;` line in your code. ([*Source*](http://en.cppreference.com/w/cpp/io/c/fprintf)) – iFreilicht Mar 20 '15 at 15:26
-
-
2Using "char bytes[required]" will allocated on stack instead of heap, it can be dangerous on large format strings. Consider using use a new instead. Yann – Yannuth Mar 16 '16 at 20:30
-
@Yannuth, I'd like to improve the answer with a mention to your comment. Could you elaborate more on the possible problems of using the stack? how would it affect the application? – Dacav Mar 30 '17 at 07:08
-
The stack allocation is limited, depending on the plateform and many parameters, but it's in megabyes. A simple test : try to put a "char tmp[10000000]" in your code, it will probably segfault. On the other hand a malloc(10000000) will easily works. The reason is that the stack and the heap have two different workings, you will find many explanations on this subject on google. To answer your question if you have a long string, it will crash the program. – Yannuth Oct 26 '17 at 12:38
If you are on a system that has asprintf(3), you can easily wrap it:
#include <iostream>
#include <cstdarg>
#include <cstdio>
std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
std::string format(const char *fmt, ...)
{
std::string result;
va_list ap;
va_start(ap, fmt);
char *tmp = 0;
int res = vasprintf(&tmp, fmt, ap);
va_end(ap);
if (res != -1) {
result = tmp;
free(tmp);
} else {
// The vasprintf call failed, either do nothing and
// fall through (will return empty string) or
// throw an exception, if your code uses those
}
return result;
}
int main(int argc, char *argv[]) {
std::string username = "you";
std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
return 0;
}

- 2,178
- 23
- 20
-
2I would add this line as a declaration before `format`, as it tells gcc to check the types of the arguments and give a decent warning with -Wall: `std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));` – Aaron McDaid Jul 11 '15 at 12:48
-
2I've just added a call to `va_end` . *"if va_end is not called before a function that calls va_start or va_copy returns, the behavior is undefined. "* - [docs](http://en.cppreference.com/w/cpp/utility/variadic/va_end) – Aaron McDaid Jul 11 '15 at 12:51
-
1You should check the return result of vasprintf as the pointer value is undefined upon failure. So, possibly include
and add: if (size == -1) { throw std::bad_alloc(); } – Goblinhack Aug 27 '17 at 23:47 -
Good point, I've modified the answer accordingly, I've decided to just put a comment there instead of doing the `throw std::bad_alloc();`, since I'm not using C++ exceptions in my codebase, and for people who do, they can easily add it based on the source comment and your comment here. – Thomas Perl Aug 28 '17 at 08:52
Based on the answer provided by Erik Aronesty:
std::string string_format(const std::string &fmt, ...) {
std::vector<char> str(100,'\0');
va_list ap;
while (1) {
va_start(ap, fmt);
auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
va_end(ap);
if ((n > -1) && (size_t(n) < str.size())) {
return str.data();
}
if (n > -1)
str.resize( n + 1 );
else
str.resize( str.size() * 2);
}
return str.data();
}
This avoids the need to cast away const
from the result of .c_str()
which was in the original answer.

- 658
- 7
- 15
-
1Building off of Erik Aronesty's answer is a red herring. His first code sample is unsafe and his second, with the loop is inefficient and clumsy. The clean implementation is clearly indicated by the fact that, if the buf_siz of any of the vprintf family of functions is zero, nothing is written and buffer may be a null pointer, however the return value (number of bytes that would be written not including the null terminator) is still calculated and returned. A production quality answer is here: https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf/49812018#49812018 – Douglas Daseeco Sep 21 '18 at 21:18
-
Erik Aronesty's answer has been edited since mine was added. I wanted to highlight the option of using vector
to store strings as they are built. I use this technique often when calling C functions from C++ code. It is interesting that the question now has 34 answers. – ChetS Oct 05 '18 at 17:30 -
The cppreference.com example on the vfprintf page was added later. I believe the best answer is the currently accepted answer, using string streams instead of a printf variant is the C++ way of things. However my answer did add value when it was provided; It was incrementally better than other answers at the time. Now the standard has string_view, parameter packs and Variadic template a new answer could include those features. As for my answer, although it may no longer be deserving of additional up-votes, it does not deserve to be deleted or down-voted, so I'm leaving it as it. – ChetS Oct 11 '18 at 17:50
inline void format(string& a_string, const char* fmt, ...)
{
va_list vl;
va_start(vl, fmt);
int size = _vscprintf( fmt, vl );
a_string.resize( ++size );
vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
va_end(vl);
}

- 67
- 1
- 1
-
2+1 for the smart idea, but it's not very clear what `_vscprintf` is. I think you should elaborate on this answer. – Dacav Oct 04 '14 at 21:24
You could try this:
string str;
str.resize( _MAX_PATH );
sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11
str.resize( strlen( str.data() ) + 1 );

- 5,085
- 11
- 36
- 38
string doesn't have what you need, but std::stringstream does. Use a stringstream to create the string and then extract the string. Here is a comprehensive list on the things you can do. For example:
cout.setprecision(10); //stringstream is a stream like cout
will give you 10 decimal places of precision when printing a double or float.

- 20,075
- 11
- 87
- 171
-
8which still doesn't give you anything near the control printf gives you... but is nice. – Erik Aronesty Aug 27 '14 at 15:16
I usually use this:
std::string myformat(const char *const fmt, ...)
{
char *buffer = NULL;
va_list ap;
va_start(ap, fmt);
(void)vasprintf(&buffer, fmt, ap);
va_end(ap);
std::string result = buffer;
free(buffer);
return result;
}
Disadvantage: not all systems support vasprint

- 433
- 4
- 17
- 38
-
vasprintf is nice - however you need to check the return code. On -1 buffer will have an undefined value. Need: if (size == -1) { throw std::bad_alloc(); } – Goblinhack Aug 27 '17 at 23:49
Took the idea from Dacav and pixelpoint's answer. I played around a bit and got this:
#include <cstdarg>
#include <cstdio>
#include <string>
std::string format(const char* fmt, ...)
{
va_list vl;
va_start(vl, fmt);
int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
va_end(vl);
char buffer[size];
va_start(vl, fmt);
size = vsnprintf(buffer, size, fmt, vl);
va_end(vl);
return std::string(buffer, size);
}
With sane programming practice I believe the code should be enough, however I'm still open to more secure alternatives that are still simple enough and would not require C++11.
And here's another version that makes use of an initial buffer to prevent second call to vsnprintf()
when initial buffer is already enough.
std::string format(const char* fmt, ...)
{
va_list vl;
int size;
enum { INITIAL_BUFFER_SIZE = 512 };
{
char buffer[INITIAL_BUFFER_SIZE];
va_start(vl, fmt);
size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
va_end(vl);
if (size < INITIAL_BUFFER_SIZE)
return std::string(buffer, size);
}
size += sizeof('\0');
char buffer[size];
va_start(vl, fmt);
size = vsnprintf(buffer, size, fmt, vl);
va_end(vl);
return std::string(buffer, size);
}
(It turns out that this version is just similar to Piti Ongmongkolkul's answer, only that it doesn't use new
and delete[]
, and also specifies a size when creating std::string
.
The idea here of not using new
and delete[]
is to imply usage of the stack over the heap since it doesn't need to call allocation and deallocation functions, however if not properly used, it could be dangerous to buffer overflows in some (perhaps old, or perhaps just vulnerable) systems. If this is a concern, I highly suggest using new
and delete[]
instead. Note that the only concern here is about the allocations as vsnprintf()
is already called with limits, so specifying a limit based on the size allocated on the second buffer would also prevent those.)

- 1
- 1

- 72,135
- 12
- 99
- 105
All the answers so far here seems to have one or more of these problems: (1) it may not work on VC++ (2) it requires additional dependencies like boost or fmt (3) its too complicated custom implementation and probably not tested well.
Below code addresses all of above issues.
#include <string>
#include <cstdarg>
#include <memory>
std::string stringf(const char* format, ...)
{
va_list args;
va_start(args, format);
#ifndef _MSC_VER
//GCC generates warning for valid use of snprintf to get
//size of result string. We suppress warning with below macro.
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
size_t size = std::snprintf(nullptr, 0, format, args) + 1; // Extra space for '\0'
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
std::unique_ptr<char[]> buf(new char[ size ] );
std::vsnprintf(buf.get(), size, format, args);
return std::string(buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
#else
int size = _vscprintf(format, args);
std::string result(size, 0);
vsnprintf_s((char*)result.data(), size + 1, _TRUNCATE, format, args);
return result;
#endif
va_end(args);
}
int main() {
float f = 3.f;
int i = 5;
std::string s = "hello!";
auto rs = stringf("i=%d, f=%f, s=%s", i, f, s.c_str());
printf("%s", rs.c_str());
return 0;
}
Notes:
- Separate VC++ code branch is necessary because VC++ has decided to deprecate
snprintf
which will generate compiler warnings for other highly voted answers above. As I always run in "warnings as errors" mode, its no go for me. - The function accepts
char *
instead ofstd::string
. This because most of the time this function would be called with literal string which is indeedchar *
, notstd::string
. In case you do havestd::string
as format parameter, then just call.c_str()
. - Name of the function is stringf instead of things like string_format to keepup with printf, scanf etc.
- It doesn't address safety issue (i.e. bad parameters can potentially cause seg fault instead of exception). If you need this then you are better off with boost or fmt libraries. My preference here would be fmt because it is just one header and source file to drop in the project while having less weird formatting syntax than boost. However both are non-compatible with printf format strings so below is still useful in that case.
- The stringf code passes through GCC strict mode compilation. This requires extra
#pragma
macros to suppress false positives in GCC warnings.
Above code was tested on,

- 433
- 3
- 6

- 63,284
- 17
- 238
- 185
-
Superb lonely answer. Always view StackOverflow pages from the bottom up! – Dino Dini Feb 10 '23 at 22:58
-
std::string result(size, 0); vsnprintf_s((char*)result.data(), size+1, _TRUNCATE, format, args); prevents an extra null character being added to the resulting string if _MSC_VER – Dino Dini Feb 11 '23 at 15:02
Below slightly modified version of @iFreilicht answer, updated to C++14 (usage of make_unique
function instead of raw declaration) and added support for std::string
arguments (based on Kenny Kerr article)
#include <iostream>
#include <memory>
#include <string>
#include <cstdio>
template <typename T>
T process_arg(T value) noexcept
{
return value;
}
template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
return value.c_str();
}
template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
const auto fmt = format.c_str();
const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
auto buf = std::make_unique<char[]>(size);
std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
auto res = std::string(buf.get(), buf.get() + size - 1);
return res;
}
int main()
{
int i = 3;
float f = 5.f;
char* s0 = "hello";
std::string s1 = "world";
std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}
Output:
i = 3, f = 5.000000, s = hello world
Feel free to merge this answer with the original one if desired.

- 149
- 1
- 9
UPDATE 1: added fmt::format
tests
I've took my own investigation around methods has introduced here and gain diametrically opposite results versus mentioned here.
I have used 4 functions over 4 methods:
- variadic function +
vsnprintf
+std::unique_ptr
- variadic function +
vsnprintf
+std::string
- variadic template function +
std::ostringstream
+std::tuple
+utility::for_each
fmt::format
function fromfmt
library
For the test backend the googletest
has used.
#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>
#include <fmt/format.h>
inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
size_t str_len = (std::max)(fmt_str.size(), string_reserve);
// plain buffer is a bit faster here than std::string::reserve
std::unique_ptr<char[]> formatted;
va_list ap;
va_start(ap, fmt_str);
while (true) {
formatted.reset(new char[str_len]);
const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);
if (final_n < 0 || final_n >= int(str_len))
str_len += (std::abs)(final_n - int(str_len) + 1);
else
break;
}
va_end(ap);
return std::string(formatted.get());
}
inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
size_t str_len = (std::max)(fmt_str.size(), string_reserve);
std::string str;
va_list ap;
va_start(ap, fmt_str);
while (true) {
str.resize(str_len);
const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);
if (final_n < 0 || final_n >= int(str_len))
str_len += (std::abs)(final_n - int(str_len) + 1);
else {
str.resize(final_n); // do not forget to shrink the size!
break;
}
}
va_end(ap);
return str;
}
template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
std::ostringstream ss;
if (string_reserve) {
ss.rdbuf()->str().reserve(string_reserve);
}
std::tuple<Args...> t{ args... };
utility::for_each(t, [&ss](auto & v)
{
ss << v;
});
return ss.str();
}
The for_each
implementation is taken from here: iterate over tuple
#include <type_traits>
#include <tuple>
namespace utility {
template <std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_each(std::tuple<Tp...> &, const FuncT &)
{
}
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_each(std::tuple<Tp...> & t, const FuncT & f)
{
f(std::get<I>(t));
for_each<I + 1, FuncT, Tp...>(t, f);
}
}
The tests:
TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
for (size_t i = 0; i < 1000000; i++) {
std::ostringstream ss;
ss << "test test test" << "+" << 12345 << "\n";
const std::string v = ss.str();
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
for (size_t i = 0; i < 1000000; i++) {
std::ostringstream ss;
ss.rdbuf()->str().reserve(256);
ss << "test test test" << "+" << 12345 << "\n";
const std::string v = ss.str();
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_fmt_format_positional)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_fmt_format_named)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
The UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR
.
unsued.hpp:
#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var) ::utility::unused_param(&var)
namespace utility {
extern const volatile void * volatile g_unused_param_storage_ptr;
extern void
#ifdef __GNUC__
__attribute__((optimize("O0")))
#endif
unused_param(const volatile void * p);
}
unused.cpp:
namespace utility {
const volatile void * volatile g_unused_param_storage_ptr = nullptr;
void
#ifdef __GNUC__
__attribute__((optimize("O0")))
#endif
unused_param(const volatile void * p)
{
g_unused_param_storage_ptr = p;
}
}
RESULTS:
[ RUN ] ExternalFuncs.test_string_format_on_unique_ptr_0
[ OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN ] ExternalFuncs.test_string_format_on_unique_ptr_256
[ OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN ] ExternalFuncs.test_string_format_on_std_string_0
[ OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN ] ExternalFuncs.test_string_format_on_std_string_256
[ OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[ OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[ OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[ OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[ OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN ] ExternalFuncs.test_fmt_format_positional
[ OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN ] ExternalFuncs.test_fmt_format_named
[ OK ] ExternalFuncs.test_fmt_format_named (392 ms)
As you can see implementation through the vsnprintf
+std::string
is equal to fmt::format
, but faster than through the vsnprintf
+std::unique_ptr
, which is faster than through the std::ostringstream
.
The tests compiled in Visual Studio 2015 Update 3
and run at Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB
.

- 2,273
- 29
- 28
(This runs fine on VC++ VS2019, VS2022)
C++17 solution (this will work for both std::string & for std::wstring):
It is not efficient to allocate a buffer, format into it & after copy it into the another string. It is possible to create std::string in the size of the formatted string & format directly into that string buffer:
#include <string>
#include <stdexcept>
#include <cwchar>
#include <cstdio>
#include <type_traits>
template<typename T, typename ... Args>
std::basic_string<T> string_format(T const* const format, Args ... args)
{
int size_signed{ 0 };
// 1) Determine size with error handling:
if constexpr (std::is_same_v<T, char>) { // C++17
size_signed = std::snprintf(nullptr, 0, format, args ...);
}
else {
size_signed = std::swprintf(nullptr, 0, format, args ...);
}
if (size_signed <= 0) {
throw std::runtime_error("error during formatting.");
}
const auto size = static_cast<size_t>(size_signed);
// 2) Prepare formatted string:
std::basic_string<T> formatted(size, T{});
if constexpr (std::is_same_v<T, char>) { // C++17
std::snprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
}
else {
std::swprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
}
return formatted; // Named Return Value Optimization (NRVO), avoids an unnecessary copy.
}
In addition: Often, the format parameter is a char[] / wchar_t[] & it is not efficient to create a std::string object. Pass char* or wchar_t* & if you already have a std::string object, you still can use it as your_string.c_str(). Example:
int main()
{
int i{ 0 };
// The format parameter is a char[] / wchar_t[]:
const std::string title1 = string_format("story[%d].", ++i); // => "story[1]"
const std::wstring title2 = string_format(L"story[%d].", ++i); // => L"story[2]"
// If you already have a std::string object:
const std::string format1{ "story[%d]." };
const std::string title3 = string_format(format1.c_str(), ++i); // => "story[3]"
const std::wstring format2{ L"story[%d]." };
const std::wstring title4 = string_format(format2.c_str(), ++i); // => L"story[4]"
}

- 645
- 1
- 3
- 19
-
why you write "T const* const format", no "const T* const format"? – Barracudach Sep 02 '22 at 22:57
-
@Barracudach The rule: "const applies to the thing left of it. If there is nothing on the left then it applies to the thing right of it.". – Amit Sep 03 '22 at 06:34
This is the code I use to do this in my program... It's nothing fancy, but it does the trick... Note, you will have to adjust your size as applicable. MAX_BUFFER for me is 1024.
std::string Format ( const char *fmt, ... )
{
char textString[MAX_BUFFER*5] = {'\0'};
// -- Empty the buffer properly to ensure no leaks.
memset(textString, '\0', sizeof(textString));
va_list args;
va_start ( args, fmt );
vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
va_end ( args );
std::string retStr = textString;
return retStr;
}

- 30,738
- 21
- 105
- 131

- 29
- 1
-
4The initialization of textString already sets the whole buffer to zero. No need to memset... – EricSchaefer Mar 03 '12 at 15:46
-
This does a full extra copy of the data, it's possible to use `vsnprintf` directly into the string. – Mooing Duck Mar 12 '13 at 16:48
Poco Foundation library has a very convenient format function, which supports std::string in both the format string and the values:

- 1,218
- 15
- 28
You can format C++ output in cout using iomanip header file. Make sure that you include iomanip header file before you use any of the helper functions like setprecision, setfill etc.
Here is a code snippet I have used in the past to print the average waiting time in the vector, which I have "accumulated".
#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>
...
cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;
Here is a brief description of how we can format C++ streams. http://www.cprogramming.com/tutorial/iomanip.html

- 139
- 1
- 6
There can be problems, if the buffer is not large enough to print the string. You must determine the length of the formatted string before printing a formatted message in there. I make own helper to this (tested on Windows and Linux GCC), and you can try use it.
String.cpp: http://pastebin.com/DnfvzyKP
String.h: http://pastebin.com/7U6iCUMa
String.cpp:
#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>
using ::std::string;
#pragma warning(disable : 4996)
#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
int length;
va_list apStrLen;
va_copy(apStrLen, ap);
length = vsnprintf(NULL, 0, format, apStrLen);
va_end(apStrLen);
if (length > 0) {
dst.resize(length);
vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
} else {
dst = "Format error! format: ";
dst.append(format);
}
}
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
va_list ap;
va_start(ap, format);
toString(dst, format, ap);
va_end(ap);
}
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
string dst;
va_list ap;
va_start(ap, format);
toString(dst, format, ap);
va_end(ap);
return dst;
}
///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
string dst;
toString(dst, format, ap);
return dst;
}
int main() {
int a = 32;
const char * str = "This works!";
string test(toString("\nSome testing: a = %d, %s\n", a, str));
printf(test.c_str());
a = 0x7fffffff;
test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
printf(test.c_str());
a = 0x80000000;
toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
printf(test.c_str());
return 0;
}
String.h:
#pragma once
#include <cstdarg>
#include <string>
using ::std::string;
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

- 30,738
- 21
- 105
- 131

- 3,021
- 2
- 18
- 17
-
With regards to the line `vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);` -- Is it safe to assume the string's buffer has room for a terminating null character? Are there implementations that do not allocate size+1 characters. Would it be safer to do `dst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);` – drwatsoncode Dec 21 '16 at 19:21
-
Apparently the answer to my previous comment is: No it is NOT safe to assume there is a null character. Specifically with regards to the C++98 spec: "Accessing the value at data()+size() produces *undefined behavior*: There are **no guarantees** that a null character terminates the character sequence pointed by the value returned by this function. See string::c_str for a function that provides such guarantee. **A program shall not alter any of the characters in this sequence.**" However, the C++11 spec indicates that `data` and `c_str` are synonyms. – drwatsoncode Dec 21 '16 at 19:35
this can be tried out. simple. really does not use nuances of the string class though.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <exception>
using namespace std;
//---------------------------------------------------------------------
class StringFormatter
{
public:
static string format(const char *format, ...);
};
string StringFormatter::format(const char *format, ...)
{
va_list argptr;
va_start(argptr, format);
char *ptr;
size_t size;
FILE *fp_mem = open_memstream(&ptr, &size);
assert(fp_mem);
vfprintf (fp_mem, format, argptr);
fclose (fp_mem);
va_end(argptr);
string ret = ptr;
free(ptr);
return ret;
}
//---------------------------------------------------------------------
int main(void)
{
string temp = StringFormatter::format("my age is %d", 100);
printf("%s\n", temp.c_str());
return 0;
}

- 189
- 2
- 9
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();

- 24,851
- 10
- 43
- 78

- 11
- 1
Very-very simple solution.
std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);

- 19
- 1
I realize this has been answered many times, but this is more concise:
std::string format(const std::string fmt_str, ...)
{
va_list ap;
char *fp = NULL;
va_start(ap, fmt_str);
vasprintf(&fp, fmt_str.c_str(), ap);
va_end(ap);
std::unique_ptr<char[]> formatted(fp);
return std::string(formatted.get());
}
example:
#include <iostream>
#include <random>
int main()
{
std::random_device r;
std::cout << format("Hello %d!\n", r());
}
See also http://rextester.com/NJB14150

- 592
- 6
- 11
Update of some answer around, difference is - function will properly accept std::string for %s
namespace format_helper
{
template <class Src>
inline Src cast(Src v)
{
return v;
}
inline const char *cast(const std::string& v)
{
return v.c_str();
}
};
template <typename... Ts>
inline std::string stringfmt (const std::string &fmt, Ts&&... vs)
{
using namespace format_helper;
char b;
size_t required = std::snprintf(&b, 0, fmt.c_str(), cast(std::forward<Ts>(vs))...);//not counting the terminating null character.
std::string result;
//because we use string as container, it adds extra 0 automatically
result.resize(required , 0);
//and snprintf will use n-1 bytes supplied
std::snprintf(const_cast<char*>(result.data()), required + 1, fmt.c_str(), cast(std::forward<Ts>(vs))...);
return result;
}
Live: http://cpp.sh/5ajsv

- 69
- 1
- 3
-
This looks neat. I have never known that `snprintf` permits a NULL buffer and returns a required number of characters. – eca2ed291a2f572f66f4a5fcf57511 Nov 22 '18 at 08:59
-
I don't like to make things complicated. This is based on iFreilicht's answer but I trimmed the noise down a bit and made it more efficient. Be aware that if you plan to use this in an interface to maybe add some fuzzy input checks.
#include <iostream>
#include <string>
template<typename... Ts>
std::string string_format( const std::string& format, Ts... Args )
{
const size_t n = std::snprintf( nullptr, 0, format.c_str(), Args ... ) + 1; // Extra space for '\0'
std::string ret(n, '\0');
std::snprintf( &ret.front(), n, format.c_str(), Args... );
return ret;
}
int main()
{
int a = 5;
char c = 'h';
double k = 10.3;
std::cout << string_format("%d, %c, %.2f", a, c, k) << "\n";
}
Output:
5, h, 10.30
(*Only caveat I found performance-wise is that there is no way to default initialize string storage. Which is a shame because we wouldn't need to value initialize everything to '\0' here.)

- 3,778
- 1
- 12
- 34
-
My latest answer is something similar, except it supports appends, warns when the format string isn't correct and ensures no copies (the output string is passed in as a reference) – ericcurtin May 21 '22 at 22:25
Here's an optimal solution in terms of memory usage (and execution speed), doesn't rely on RVO and can also do appends if string size is greater that zero, will also automatically resize the std::string.
The macro solution IMO is better, modern compilers will warn if the format string does not match the type. This warning will not occur with the function version as the compiler cannot see the snprintf. The macro version is also much shorter and it also requires one less include.
From:
https://github.com/ericcurtin/twincam
macro solution:
#include <string.h>
#include <string>
// function that will sprintf to a C++ string starting from std::string::size()
// so if you want to completely overwrite a string or start at a specific point
// use std::string::clear() or std::string::resize(). str is a std::string.
#define STRING_PRINTF(str, ...) \
do { \
const int size = snprintf(NULL, 0, __VA_ARGS__); \
const size_t start_of_string = str.size(); \
str.resize(start_of_string + size); \
snprintf(&str[start_of_string], str.size() + 1, __VA_ARGS__); \
} while (0)
function solution:
#include <stdarg.h> // For va_start, etc.
#include <string.h>
#include <string>
// function that will sprintf to a C++ string starting from std::string::size()
// so if you want to completely overwrite a string or start at a specific point
// use std::string::clear() or std::string::resize()
int string_printf(std::string& str, const char* const fmt, ...) {
c_va_list c_args;
va_start(c_args.args, fmt);
c_va_list tmpa;
va_copy(tmpa.args, c_args.args);
// Get addtional size required
int size = vsnprintf(NULL, 0, fmt, tmpa.args);
if (size < 0) {
return -1;
}
const size_t start_of_string = str.size();
str.resize(start_of_string + size);
// plus 1 so the null terminator gets included
size = vsnprintf(&str[start_of_string], str.size() + 1, fmt, c_args.args);
return size;
}
even more optimal solution:
#define STRING_PRINTF(str, ...) \
do { \
const size_t write_point = str.size(); \
str.resize(write_point + 127); \
const int size = snprintf(&str[write_point], 128, __VA_ARGS__); \
str.resize(write_point + size); \
if (size < 128) { \
break; \
} \
\
snprintf(&str[write_point], size + 1, __VA_ARGS__); \
} while (0)
This is an even more optimal solution, takes a guess that (kudos to folly from meta), the sprintf will be less than 128 bytes, if yes the format string is only parsed once instead of twice.

- 1,499
- 17
- 20
Here my (simple solution):
std::string Format(const char* lpszFormat, ...)
{
// Warning : "vsnprintf" crashes with an access violation
// exception if lpszFormat is not a "const char*" (for example, const string&)
size_t nSize = 1024;
char *lpBuffer = (char*)malloc(nSize);
va_list lpParams;
while (true)
{
va_start(lpParams, lpszFormat);
int nResult = vsnprintf(
lpBuffer,
nSize,
lpszFormat,
lpParams
);
va_end(lpParams);
if ((nResult >= 0) && (nResult < (int)nSize) )
{
// Success
lpBuffer[nResult] = '\0';
std::string sResult(lpBuffer);
free (lpBuffer);
return sResult;
}
else
{
// Increase buffer
nSize =
(nResult < 0)
? nSize *= 2
: (nResult + 1)
;
lpBuffer = (char *)realloc(lpBuffer, nSize);
}
}
}

- 30,738
- 21
- 105
- 131

- 8,891
- 5
- 36
- 74
One solution I've favoured is to do this with sprintf directly into the std::string buffer, after making said buffer big enough:
#include <string>
#include <iostream>
using namespace std;
string l_output;
l_output.resize(100);
for (int i = 0; i < 1000; ++i)
{
memset (&l_output[0], 0, 100);
sprintf (&l_output[0], "\r%i\0", i);
cout << l_output;
cout.flush();
}
So, create the std::string, resize it, access its buffer directly...

- 17
- 1
-
Is this defined behaviour? It is pretty clear that this will most likely work, but is it actually allowed by the standard? – iFreilicht Oct 06 '14 at 17:48
I gave it a try, with regular expressions. I implemented it for ints and const strings as an example, but you can add whatever other types (POD types but with pointers you can print anything).
#include <assert.h>
#include <cstdarg>
#include <string>
#include <sstream>
#include <regex>
static std::string
formatArg(std::string argDescr, va_list args) {
std::stringstream ss;
if (argDescr == "i") {
int val = va_arg(args, int);
ss << val;
return ss.str();
}
if (argDescr == "s") {
const char *val = va_arg(args, const char*);
ss << val;
return ss.str();
}
assert(0); //Not implemented
}
std::string format(std::string fmt, ...) {
std::string result(fmt);
va_list args;
va_start(args, fmt);
std::regex e("\\{([^\\{\\}]+)\\}");
std::smatch m;
while (std::regex_search(fmt, m, e)) {
std::string formattedArg = formatArg(m[1].str(), args);
fmt.replace(m.position(), m.length(), formattedArg);
}
va_end(args);
return fmt;
}
Here is an example of use of it:
std::string formatted = format("I am {s} and I have {i} cats", "bob", 3);
std::cout << formatted << std::endl;
Output:
I am bob and I have 3 cats

- 30,738
- 21
- 105
- 131

- 2,027
- 1
- 16
- 20
This is a Windows specific solution designed to avoid compiler warnings in Visual Studio without silencing them. The warnings in question are for using an std::string with va_start, which produces a warning erroneously, and for using deprecated printf variants.
template<typename ... va>
std::string Format( const std::string& format, va ... args )
{
std::string s;
s.resize( _scprintf( format.c_str(), args ... ) + 1 );
s.resize( _snprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
return s;
}
template<typename ... va>
std::wstring Format( const std::wstring& format, va ... args )
{
std::wstring s;
s.resize( _scwprintf( format.c_str(), args ... ) + 1 );
s.resize( _snwprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
return s;
}
std::string s = Format( "%hs %d", "abc", 123 );
std::wstring ws = Format( L"%hs %d", "abc", 123 );

- 11
- 2
I will now write version for Visual Studio, hopefully someday someone will make it portable. (Suspect need to replace _vsnwprintf
with vsnwprintf
and something like this.)
You need to disable deprecate warnings by using define _CRT_SECURE_NO_WARNINGS
from project configuration.
I'm using _vsnwprintf
with first parameter as a nullptr
to be able to estimate buffer size, reserve wstring buffer, and then formatting string directly into buffer.
Not sure why you need to disable deprecated warning, as safe versions of same method call (_vsnwprintf_s
) cannot use nullptr
as an input. Suspect needs to be reported to Microsoft C++ team.
This version should be working with both - string
or wstring
classes.
If you find any bug or inconsistency, please ask again, I'll try to fix it.
stringHelpers.h:
#pragma once
#include <string>
//
// Formats string/wstring according to format, if formatting fails (e.g. invalid %s pointer - returns empty string)
//
template <typename T>
std::basic_string<T> sFormat(const T* format, ...)
{
va_list args;
va_start(args, format);
int size;
if constexpr (std::is_same_v<T, char>)
size = vsnprintf(nullptr, 0, format, args);
else
size = _vsnwprintf(nullptr, 0, format, args);
size++; // Zero termination
std::basic_string<T> s;
s.resize(size);
if constexpr (std::is_same_v<T, char>)
vsnprintf(&s[0], size, format, args);
else
_vsnwprintf(&s[0], size, format, args);
va_end(args);
return s;
}
Above is sample of code, which can be copied as such. I will maintain working version in my own repository in github:
https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/helpers.h#L12

- 4,723
- 2
- 50
- 62
For Visual C:
std::wstring stringFormat(const wchar_t* fmt, ...)
{
if (!fmt) {
return L"";
}
std::vector<wchar_t> buff;
size_t size = wcslen(fmt) * 2;
buff.resize(size);
va_list ap;
va_start(ap, fmt);
while (true) {
int ret = _vsnwprintf_s(buff.data(), size, _TRUNCATE, fmt, ap);
if (ret != -1)
break;
else {
size *= 2;
buff.resize(size);
}
}
va_end(ap);
return std::wstring(buff.data());
}

- 30,738
- 21
- 105
- 131

- 40,341
- 47
- 125
- 198
This question already solved. But, I think this is another way to format string
in c++
class string_format {
private:
std::string _result;
public:
string_format( ) { }
~string_format( ) { std::string( ).swap( _result ); }
const std::string& get_data( ) const { return _result; }
template<typename T, typename... Targs>
void format( const char* fmt, T value, Targs... Fargs ) {
for ( ; *fmt != '\0'; fmt++ ) {
if ( *fmt == '%' ) {
_result += value;
this->format( fmt + 1, Fargs..., 0 ); // recursive call
return;
}
_result += *fmt;
}
}
friend std::ostream& operator<<( std::ostream& ostream, const string_format& inst );
};
inline std::string& operator+=( std::string& str, int val ) {
str.append( std::to_string( val ) );
return str;
}
inline std::string& operator+=( std::string& str, double val ) {
str.append( std::to_string( val ) );
return str;
}
inline std::string& operator+=( std::string& str, bool val ) {
str.append( val ? "true" : "false" );
return str;
}
inline std::ostream& operator<<( std::ostream& ostream, const string_format& inst ) {
ostream << inst.get_data( );
return ostream;
}
and test this class as:
string_format fmt;
fmt.format( "Hello % and is working ? Ans: %", "world", true );
std::cout << fmt;
and you may check it here

- 800
- 10
- 22
-
Crazy... What's wrong with this answer. Please, let me know to fix my think. – Rajib Chy Feb 06 '22 at 17:31
Windows and Visual Studio have a mighty attractive solution: CString.
CString str;
str.Format("Hello %s\n", "World");
str = "ABC";
str += "DEF";

- 4,114
- 2
- 34
- 39