std::string formatting like sprintf

1,066,639

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.

Share:
1,066,639
Hanut
Author by

Hanut

Updated on July 15, 2022

Comments

  • Hanut
    Hanut almost 2 years

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

  • John Dibling
    John Dibling about 14 years
    The magic cookie in char buf[100]; makes this solution not very robust. But the essential idea is there.
  • Hassan Syed
    Hassan Syed about 14 years
    you can prune the libraries you need out of boost as well. Using a suplied tool.
  • EricSchaefer
    EricSchaefer about 12 years
    The initialization of textString already sets the whole buffer to zero. No need to memset...
  • 0xDEAD BEEF
    0xDEAD BEEF almost 12 years
    slow. why increase size by 1? And when does this funciton return -1?
  • scrub
    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
    quantum over 11 years
    You are overwriting str.c_str()? Isn't that dangerous?
  • John McFarlane
    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
    Steve Hanov over 11 years
    va_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.
    Doug T. over 11 years
    I +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
    Jimbo over 11 years
    John,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
    Dan about 11 years
    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.
  • slashmais
    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
    Mooing Duck about 11 years
    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
    Mooing Duck about 11 years
    This does a full extra copy of the data, it's possible to use vsnprintf directly into the string.
  • Mooing Duck
    Mooing Duck about 11 years
    This does a full extra copy of the data, it's possible to use vsnprintf directly into the string.
  • Erik Aronesty
    Erik Aronesty about 11 years
    for 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
    Masood Khaari almost 11 years
    For 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
    Masood Khaari almost 11 years
    And 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 of vsnprintf().
  • Masood Khaari
    Masood Khaari almost 11 years
    Use 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
    Martijn Courteaux almost 11 years
    The 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
    Paul Du Bois over 10 years
    But beware, because MSVC _vsnprintf acts differently from vsnprintf when it comes to when and how it returns the size.
  • samoz
    samoz over 10 years
    Won't returning the string object that was created on the stack be invalid?
  • Josh Haberman
    Josh Haberman over 10 years
    I'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
    ChetS over 10 years
    @miki725 Why &formatted[0] instead of formatted.get()? (Also my answer uses std::vector<char> instead of std::unique_ptr<char[]> with the same goal of safe code but I am unsure about efficient.)
  • DarioP
    DarioP about 10 years
    in the improved implementation std::string str is not used ;)
  • vitaut
    vitaut almost 10 years
  • Erik Aronesty
    Erik Aronesty almost 10 years
    The "safer and better" version doesn't compile for me: error: "unique_ptr" is not a member of "std"
  • Erik Aronesty
    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
    Erik Aronesty over 9 years
    @PaulDuBois That's precisely why the first version exists. To deal with O/S specific weirdness.
  • Erik Aronesty
    Erik Aronesty over 9 years
    which still doesn't give you anything near the control printf gives you... but is nice.
  • Dacav
    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
    iFreilicht over 9 years
    Is this defined behaviour? It is pretty clear that this will most likely work, but is it actually allowed by the standard?
  • alexk7
    alexk7 over 9 years
    snprintf always appends a terminating null-byte but returns the number of characters without it. Doesn't this code always skip the last character?
  • Dacav
    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 the required integer (which fortunately at that point is already evaluated).
  • cha
    cha about 9 years
    Please 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
    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 fact 0 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
    iFreilicht about 9 years
    Just 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)
  • cha
    cha about 9 years
    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
  • Dacav
    Dacav about 9 years
    @iFreilicht, fix'd. Also +1
  • iFreilicht
    iFreilicht about 9 years
    What if the count is zero and buffer is not a null pointer? Will that call return -1 as well?
  • Luis Vito
    Luis Vito about 9 years
    For 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
    Luis Vito about 9 years
    Including 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
    moooeeeep about 9 years
    Why don't you use std::vector<char> buf(size); as buffer and return simply return &buf[0] ?
  • iFreilicht
    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 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.
  • moooeeeep
    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 a std::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
    Phil Williams almost 9 years
    I 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.
  • Aaron McDaid
    Aaron McDaid almost 9 years
    Even 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
    Aaron McDaid almost 9 years
    I 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
    Aaron McDaid almost 9 years
    I'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
    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
    Anh Tuan over 8 years
    I 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.
  • Ben
    Ben over 8 years
    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?
  • iFreilicht
    iFreilicht over 8 years
    @Ben C++ doesn't allow Variable Length Arrays like C does, refer to this question for further details.
  • iFreilicht
    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
    xor007 over 8 years
    Unfortunatelly, 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
    Dietrich Epp over 8 years
    "You don't have write access to the buffer..." This is just incorrect.
  • Yannuth
    Yannuth about 8 years
    Using "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
    Shital Shah about 8 years
    std::snprintf is not available in VC++12 (Visual Studio 2013). Replace it with _snprintf instead.
  • villapx
    villapx about 8 years
    @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]: "The program shall not alter any of the values stored in the character array." ...unless there's something I'm missing?
  • Dietrich Epp
    Dietrich Epp about 8 years
    @villapx: There is something you are missing. See this answer: stackoverflow.com/questions/25169915/…
  • villapx
    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
    Behrouz.M almost 8 years
    why you do not use char buf[length + 1]; instead of char* buf = new char[length + 1]; ?
  • user2622016
    user2622016 almost 8 years
    The 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]
  • Erik Aronesty
    Erik Aronesty almost 8 years
    For those who are concerned with using strings as writable buffers, please see: stackoverflow.com/questions/25169915/…
  • Erik Aronesty
    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
    Sleepi almost 8 years
    My 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: stackoverflow.com/q/9707569/143504 and stackoverflow.com/q/9306175/143504.
  • iFreilicht
    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
    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
    Snooze over 7 years
    Which 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.
  • iFreilicht
    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
    Owl over 7 years
    Write your own variadic function to take in printf like formatting options.
  • Stephan
    Stephan over 7 years
    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.
  • Eddie Deng
    Eddie Deng over 7 years
    very clever idea to get the actual buff size needed for formatted output
  • Markus Weber
    Markus Weber over 7 years
    to use osstream add #include <sstream> at the beginning of your program.
  • Zitrax
    Zitrax over 7 years
    @AaronMcDaid asprintf is a GNU extension, thus not portable.
  • drwatsoncode
    drwatsoncode over 7 years
    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
    drwatsoncode over 7 years
    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.
  • Zitrax
    Zitrax about 7 years
    Passing 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
    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 print std::strings, see this question for reference.
  • Zitrax
    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
    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
    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
    ManuelAtWork almost 7 years
    The 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
    iFreilicht almost 7 years
    @ManuelAtWork Good point, that is a valid reason to consider a solution with va_args over this one.
  • Ruslan
    Ruslan almost 7 years
    @raypixar because VLAs are a C99 feature unsupported in standard C++ (and it even became optional starting from C11).
  • Goblinhack
    Goblinhack over 6 years
    You 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
    Goblinhack over 6 years
    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(); }
  • Thomas Perl
    Thomas Perl over 6 years
    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.
  • Yannuth
    Yannuth over 6 years
    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.
  • Joe
    Joe over 6 years
    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.
  • Zitrax
    Zitrax over 6 years
    Earlier 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
    iFreilicht over 6 years
    @Zitrax I added it to the answer, that's probably going to be very useful.
  • Pavel P
    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
    Sérgio about 6 years
    IMHO you miss the include error: 'fmt' has not been declared
  • vitaut
    vitaut about 6 years
    This 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
    Sérgio about 6 years
    for me is not so obvious , IMHO you should include it in snippet , thanks for the feedback
  • Valentin
    Valentin about 6 years
    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?
  • iFreilicht
    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 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.
  • Douglas Daseeco
    Douglas Daseeco almost 6 years
    Neither code snippet is particularly safe or simple, and neither is any faster than the production quality answer below (stackoverflow.com/questions/2342162/…).
  • Douglas Daseeco
    Douglas Daseeco almost 6 years
    Adding an entire library for such a simple thing is not nessecary. This was answered at stackoverflow.com/questions/19009094/….
  • Erik Aronesty
    Erik Aronesty over 5 years
    @quantum it is save according to the specifications, yes. memory is guaranteed to be allocated contiguously.
  • Douglas Daseeco
    Douglas Daseeco over 5 years
    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
    Douglas Daseeco over 5 years
    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: stackoverflow.com/questions/2342162/…
  • Douglas Daseeco
    Douglas Daseeco over 5 years
    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: stackoverflow.com/questions/2342162/…
  • Douglas Daseeco
    Douglas Daseeco over 5 years
    Building 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
    Douglas Daseeco over 5 years
    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: stackoverflow.com/questions/2342162/…
  • Erik Aronesty
    Erik Aronesty over 5 years
    However, when performance testing the "correct answer" is slow and uses twice the number of memory allocations.
  • ChetS
    ChetS over 5 years
    Erik 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
    Steven Eckhoff over 5 years
    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.
  • iFreilicht
    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
    Steven Eckhoff over 5 years
    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.
  • ChetS
    ChetS over 5 years
    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.
  • Andry
    Andry over 5 years
    I did mine own investigation here and gain less performance results versus plain snprints: stackoverflow.com/questions/2342162/…
  • Andry
    Andry over 5 years
    I did mine own investigation here and gain diametrically opposite results: stackoverflow.com/questions/2342162/…
  • Douglas Daseeco
    Douglas Daseeco over 5 years
    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: stackoverflow.com/questions/2342162/…
  • b1sub
    b1sub over 5 years
    This looks neat. I have never known that snprintf permits a NULL buffer and returns a required number of characters.
  • Alex Zaharov
    Alex Zaharov over 5 years
    1st - thanks for comment :) 2nd - I can only say RTFM.
  • Engineerist
    Engineerist over 5 years
    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
    Engineerist over 5 years
    FWIW, 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
    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
    Mihai Todor almost 5 years
    @user2622016: Thanks for the solution! Please note that std::move is superfluous.
  • Ciro Santilli OurBigBook.com
    Ciro Santilli OurBigBook.com almost 5 years
    An fmt like implementation was added to C++20! stackoverflow.com/a/57286312/895245 fmt currently claims support for it. Awesome work!
  • ead
    ead over 4 years
    A 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
    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
    ead over 4 years
    One 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
    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
    Matthias Urlichs about 4 years
    This unfortunately doesn't work with GCC's -Werror=format-security.
  • user2622016
    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
    Chris over 3 years
    Why 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
    Jerry Jeremiah over 3 years
    So why do you use a std::vector<char> instead of a std::string ?
  • moodboom
    moodboom over 3 years
    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, it's not anywhere yet...
  • iFreilicht
    iFreilicht over 3 years
    @moodboom That's very good to know, I'll add it to the answer.
  • asad_nitp
    asad_nitp over 3 years
    what about %g ?
  • artless noise
    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 adds std::format to heap more irony on past arguments.
  • Milind Morey
    Milind Morey about 3 years
    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 "<<x+1; string s2=s1.str();
  • Admin
    Admin about 3 years
    he 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
    Andrew Domaszek almost 3 years
    Won't this segfault on strcpy(..) when new char[n] fails?
  • Darrin Cullop
    Darrin Cullop over 2 years
    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). gist.github.com/dwcullop/aabb2007ba0dcd8c7e80a761545e6ca3
  • Darrin Cullop
    Darrin Cullop over 2 years
    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.
  • Rajib Chy
    Rajib Chy about 2 years
    Crazy... What's wrong with this answer. Please, let me know to fix my think.
  • jdknight
    jdknight about 2 years
    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?
  • iFreilicht
    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 :)