std::string formatting like sprintf
Solution 1
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();
Solution 2
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 2021-11-28, partial support is available in Visual Studio 2019 16.10, which was released on 2021-05-25 and Clang 14, which is tracked here. 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.
Solution 3
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.
Solution 4
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"
Solution 5
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 will be able to format std::string
and write it to a stream:
std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);
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("Look, a string: {}", s);
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
.
Hanut
Updated on July 15, 2022Comments
-
Hanut almost 2 years
I have to format
std::string
withsprintf
and send it into file stream. How can I do this? -
John Dibling about 14 yearsThe magic cookie in
char buf[100];
makes this solution not very robust. But the essential idea is there. -
Hassan Syed about 14 yearsyou can prune the libraries you need out of boost as well. Using a suplied tool.
-
EricSchaefer about 12 yearsThe initialization of textString already sets the whole buffer to zero. No need to memset...
-
0xDEAD BEEF almost 12 yearsslow. why increase size by 1? And when does this funciton return -1?
-
scrub almost 12 years@dead beef: origin of this code is from the documentation of vsnprintf ( tin.org/bin/man.cgi?section=3&topic=vsnprintf ), there in the comments your questions are answered.
-
quantum over 11 yearsYou are overwriting str.c_str()? Isn't that dangerous?
-
John McFarlane over 11 years@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. -
Steve Hanov over 11 yearsva_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.
-
Doug T. over 11 yearsI +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.
-
Jimbo over 11 yearsJohn,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.
-
Dan about 11 yearsPlease 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.
-
slashmais about 11 years@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? -
Mooing Duck about 11 yearsI 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 about 11 yearsThis does a full extra copy of the data, it's possible to use
vsnprintf
directly into the string. -
Mooing Duck about 11 yearsThis does a full extra copy of the data, it's possible to use
vsnprintf
directly into the string. -
Erik Aronesty about 11 yearsfor 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
-
Masood Khaari almost 11 yearsFor reference arguments, it's not MSVC problem. It's required by the standard: cplusplus.com/reference/cstdarg/va_start (See requirements for
paramN
in C++.) -
Masood Khaari almost 11 yearsAnd to obtain the resulting string length beforehand, see: 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 ofvsnprintf()
. -
Masood Khaari almost 11 yearsUse the code in 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<char[]> buffer (new char[size]);
-
Martijn Courteaux almost 11 yearsThe 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.
-
Paul Du Bois over 10 yearsBut beware, because MSVC _vsnprintf acts differently from vsnprintf when it comes to when and how it returns the size.
-
samoz over 10 yearsWon't returning the string object that was created on the stack be invalid?
-
Josh Haberman over 10 yearsI'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: github.com/haberman/upb/blob/…
-
ChetS over 10 years@miki725 Why
&formatted[0]
instead offormatted.get()
? (Also my answer usesstd::vector<char>
instead ofstd::unique_ptr<char[]>
with the same goal of safe code but I am unsure about efficient.) -
DarioP about 10 yearsin the improved implementation
std::string str
is not used ;) -
vitaut almost 10 yearsBoost Format is not only big, but also very slow. See zverovich.net/2013/09/07/… and boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
-
Erik Aronesty almost 10 yearsThe "safer and better" version doesn't compile for me: error: "unique_ptr" is not a member of "std"
-
Erik Aronesty over 9 years@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 over 9 years@PaulDuBois That's precisely why the first version exists. To deal with O/S specific weirdness.
-
Erik Aronesty over 9 yearswhich still doesn't give you anything near the control printf gives you... but is nice.
-
Dacav over 9 years+1 for the smart idea, but it's not very clear what
_vscprintf
is. I think you should elaborate on this answer. -
iFreilicht over 9 yearsIs this defined behaviour? It is pretty clear that this will most likely work, but is it actually allowed by the standard?
-
alexk7 over 9 yearssnprintf always appends a terminating null-byte but returns the number of characters without it. Doesn't this code always skip the last character?
-
Dacav over 9 years@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 therequired
integer (which fortunately at that point is already evaluated). -
cha about 9 yearsPlease emphasise in your answer for the Visual Studio users that the version of VS must be at least 2013. From this 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.
-
iFreilicht about 9 years@cha Did you actually try this? From the doc of this function for VS12 and earlier: 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 fact0
in my code, this neither contradicts the quote you posted nor the phrasing on cppreference: [...] If bufsz is zero, nothing is written and buffer may be a null pointer[...] -
iFreilicht about 9 yearsJust a small hint: With a buffer size of 0, you can pass a
nullptr
as the buffer argument, eliminating thechar b;
line in your code. (Source) -
cha about 9 yearsThe 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
-
Dacav about 9 years@iFreilicht, fix'd. Also +1
-
iFreilicht about 9 yearsWhat if the
count
is zero andbuffer
is not a null pointer? Will that call return -1 as well? -
Luis Vito about 9 yearsFor 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.
-
Luis Vito about 9 yearsIncluding 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.
-
moooeeeep about 9 yearsWhy don't you use
std::vector<char> buf(size);
as buffer and return simplyreturn &buf[0]
? -
iFreilicht about 9 years@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 anstd::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. -
moooeeeep about 9 years@iFreilicht A new
std::string
will be constructed from the implicitly converted vector (copy initialization), which is then returned as a copy, as the function signature suggests. Also, the elements of astd::vector
are, and were always intended to be, stored contiguously. But I take your point that there might be no benefit in doing so. -
Phil Williams almost 9 yearsI really like this solution, however I think the line
return string(buf.get(), buf.get() + size);
should bereturn 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. -
Aaron McDaid almost 9 yearsEven better, use
asprintf
, which allocates a new string with enough space to hold the result. Then copy that to anstd::string
if you like, and remember tofree
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 adouble
where a%s
is expected -
Aaron McDaid almost 9 yearsI 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 almost 9 yearsI'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 -
AturSams over 8 years@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?
-
Anh Tuan over 8 yearsI like your simple and neat answer, but using the
unique_ptr
is pretty over-killed here (and we have to includememory.h
). Just allocate an array of character and free it after using works fine. -
Ben over 8 yearsDo you mind to explain why one would allocate
buf
on the heap after all? Wouldn'tchar buf [size];
be sufficient to use in this case? -
iFreilicht over 8 years@Ben C++ doesn't allow Variable Length Arrays like C does, refer to this question for further details.
-
iFreilicht over 8 years@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.
-
xor007 over 8 yearsUnfortunatelly, 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.
-
Dietrich Epp over 8 years"You don't have write access to the buffer..." This is just incorrect.
-
Yannuth about 8 yearsUsing "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
-
Shital Shah about 8 yearsstd::snprintf is not available in VC++12 (Visual Studio 2013). Replace it with _snprintf instead.
-
villapx about 8 years@DietrichEpp No, it's correct. While you can obtain a
const char*
pointer andconst_cast
it, the standard explicitly states that you are not allowed to modify it. See [string.accessors]: "The program shall not alter any of the values stored in the character array." ...unless there's something I'm missing? -
Dietrich Epp about 8 years@villapx: There is something you are missing. See this answer: stackoverflow.com/questions/25169915/…
-
villapx about 8 years@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 :)
-
Behrouz.M almost 8 yearswhy you do not use
char buf[length + 1];
instead ofchar* buf = new char[length + 1];
? -
user2622016 almost 8 yearsThe difference between using
char[]
andchar*
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 usenew
. For example on my machinestring_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 arraynew char[length + 1]
-
Erik Aronesty almost 8 yearsFor those who are concerned with using strings as writable buffers, please see: stackoverflow.com/questions/25169915/…
-
Erik Aronesty almost 8 years@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.
-
Sleepi almost 8 yearsMy 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 theformat
argument tosnprintf()
is a variable, not a literal, and could result in security issues. More here: stackoverflow.com/q/9707569/143504 and stackoverflow.com/q/9306175/143504. -
iFreilicht almost 8 years@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.
-
Sleepi almost 8 years@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.
-
Snooze over 7 yearsWhich std::string constructor does the line
return string( buf.get(), buf.get() + size - 1 );
use? I figure you would usestring (const char* s, size_t n);
and thus don't understand the need forbuf.get()
in the second argument. -
iFreilicht over 7 years@Snooze It's actually using the range constructor. See (6) here: en.cppreference.com/w/cpp/string/basic_string/basic_string
-
Owl over 7 yearsWrite your own variadic function to take in printf like formatting options.
-
Stephan over 7 yearsNice 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.
-
Eddie Deng over 7 yearsvery clever idea to get the actual buff size needed for formatted output
-
Markus Weber over 7 yearsto use osstream add
#include <sstream>
at the beginning of your program. -
Zitrax over 7 years@AaronMcDaid
asprintf
is a GNU extension, thus not portable. -
drwatsoncode over 7 yearsWith 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 dodst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
-
drwatsoncode over 7 yearsApparently 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
andc_str
are synonyms. -
Zitrax about 7 yearsPassing a std::string to %s cause a compile error (error: cannot pass object of non-trivial type 'std::__cxx11::basic_string<char>' 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 ?
-
iFreilicht about 7 years@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 printstd::strings
, see this question for reference. -
Zitrax about 7 years@iFreilicht Apparently gcc also compiles it ( See godbolt comparison ). 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. -
iFreilicht about 7 years@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.
-
Dacav about 7 years@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?
-
ManuelAtWork almost 7 yearsThe 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)
-
iFreilicht almost 7 years@ManuelAtWork Good point, that is a valid reason to consider a solution with va_args over this one.
-
Ruslan almost 7 years@raypixar because VLAs are a C99 feature unsupported in standard C++ (and it even became optional starting from C11).
-
Goblinhack over 6 yearsYou should check the return result of vasprintf as the pointer value is undefined upon failure. So, possibly include <new> and add: if (size == -1) { throw std::bad_alloc(); }
-
Goblinhack over 6 yearsvasprintf 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(); }
-
Thomas Perl over 6 yearsGood 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. -
Yannuth over 6 yearsThe 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.
-
Joe over 6 yearsThe 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.
-
Zitrax over 6 yearsEarlier 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: gist.github.com/Zitrax/a2e0040d301bf4b8ef8101c0b1e3f1d5
-
iFreilicht over 6 years@Zitrax I added it to the answer, that's probably going to be very useful.
-
Pavel P about 6 years
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. And here's how printf version looks like -
Sérgio about 6 yearsIMHO you miss the include
error: 'fmt' has not been declared
-
vitaut about 6 yearsThis is just a snippet, not a complete code. Obviously you need to include <fmt/format.h> and put the code in a function.
-
Sérgio about 6 yearsfor me is not so obvious , IMHO you should include it in snippet , thanks for the feedback
-
Valentin about 6 yearsI'm confused how we can pass a parameter pack
args ...
intosnprintf
which takes c-style variadic arguments. Aren't they two different things? -
iFreilicht about 6 years@Valentin yes they are, but they are compatible this way around.
args ...
is a parameter pack expansion 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 theva_args
mechanism insidesnprintf
just like you'd expect. It doesn't know that it receives arguments from a parameter pack, it only sees the individual arguments. -
Douglas Daseeco almost 6 yearsNeither code snippet is particularly safe or simple, and neither is any faster than the production quality answer below (stackoverflow.com/questions/2342162/…).
-
Douglas Daseeco almost 6 yearsAdding an entire library for such a simple thing is not nessecary. This was answered at stackoverflow.com/questions/19009094/….
-
Erik Aronesty over 5 years@quantum it is save according to the specifications, yes. memory is guaranteed to be allocated contiguously.
-
Douglas Daseeco over 5 yearsUsing 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 over 5 yearsUsing 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: stackoverflow.com/questions/2342162/…
-
Douglas Daseeco over 5 yearsBuilding 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: stackoverflow.com/questions/2342162/…
-
Douglas Daseeco over 5 yearsBuilding 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: stackoverflow.com/questions/2342162/…
-
Douglas Daseeco over 5 yearsBuilding 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: stackoverflow.com/questions/2342162/…
-
Erik Aronesty over 5 yearsHowever, when performance testing the "correct answer" is slow and uses twice the number of memory allocations.
-
ChetS over 5 yearsErik Aronesty's answer has been edited since mine was added. I wanted to highlight the option of using vector<char> 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.
-
Steven Eckhoff over 5 yearsI 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.
-
iFreilicht over 5 years@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?
-
Steven Eckhoff over 5 yearsFrom 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.
-
ChetS over 5 yearsThe 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.
-
Andry over 5 yearsI did mine own investigation here and gain less performance results versus plain
snprints
: stackoverflow.com/questions/2342162/… -
Andry over 5 yearsI did mine own investigation here and gain diametrically opposite results: stackoverflow.com/questions/2342162/…
-
Douglas Daseeco over 5 yearsThis 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: stackoverflow.com/questions/2342162/…
-
b1sub over 5 yearsThis looks neat. I have never known that
snprintf
permits a NULL buffer and returns a required number of characters. -
Alex Zaharov over 5 years1st - thanks for comment :) 2nd - I can only say RTFM.
-
Engineerist over 5 yearsBrief 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 over 5 yearsFWIW, 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.
-
Douglas Daseeco over 5 years@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.
-
Mihai Todor almost 5 years@user2622016: Thanks for the solution! Please note that
std::move
is superfluous. -
Ciro Santilli OurBigBook.com almost 5 yearsAn
fmt
like implementation was added to C++20! stackoverflow.com/a/57286312/895245 fmt currently claims support for it. Awesome work! -
ead over 4 yearsA 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).
-
iFreilicht over 4 years@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.
-
ead over 4 yearsOne 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.
-
iFreilicht over 4 years@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.
-
Matthias Urlichs about 4 yearsThis unfortunately doesn't work with GCC's
-Werror=format-security
. -
user2622016 about 4 years@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. -
Chris over 3 yearsWhy 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.
-
Jerry Jeremiah over 3 yearsSo why do you use a
std::vector<char>
instead of astd::string
? -
moodboom over 3 yearsstd::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, it's not anywhere yet...
-
iFreilicht over 3 years@moodboom That's very good to know, I'll add it to the answer.
-
asad_nitp over 3 yearswhat about %g ?
-
artless noise over 3 years@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 addsstd::format
to heap more irony on past arguments. -
Milind Morey about 3 yearsAn 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 "<<x+1; string s2=s1.str();
-
Admin about 3 yearshe doesnt want to use string streams because they are bloated AF, have you thought why all modern languages have a string formatting functionality ?
-
Andrew Domaszek almost 3 yearsWon't this segfault on
strcpy(..)
whennew char[n]
fails? -
Darrin Cullop over 2 yearsOf all the answers, this one is has the two things you want: Using C++ type safety (no
va_list
) and it writes directly into thestd::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 supportsstd::wstring
). gist.github.com/dwcullop/aabb2007ba0dcd8c7e80a761545e6ca3 -
Darrin Cullop over 2 yearsIt 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.
-
Rajib Chy about 2 yearsCrazy... What's wrong with this answer. Please, let me know to fix my think.
-
jdknight about 2 yearsA 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 utilizesmake_unique
? -
iFreilicht about 2 years@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 :)