Create a C++ string using printf-style formatting It's often convenient to use C-style printf format strings when writing C++.  I often find the modifiers much simpler to use than C++ I/O manipulators, and if I'm cribbing from existing C code, it helps to be able to re-use the existing format strings.

Here's my take on creating a new std::string given a format and the corresponding arguments.  I've given it an annotation so GCC can check the argument types agree when the format is a literal, but I don't know how to help other compilers.

I've provided a more memory-efficient version for C++17, which provides read/write access to the underlying array of a string.  My copy of CPP Reference still says "Modifying the character array accessed through data() has undefined behavior", but the Web version has been edited (May 2017) to indicate that it's only the const version that has that constraint.

For earlier standards (I require minimum C++11), we may need to allocate a temporary array, as we can't write to a string's data.  Unfortunately, this requires an extra allocation and copy.

#include &lt;string&gt;

std::string format(const char *fmt, ...)
#ifdef __GNUC__
    __attribute__ ((format (printf, 1, 2)))
#endif
    ;




// Implementation

#include &lt;cstdio&gt;
#include &lt;cstdarg&gt;

#if __cplusplus &lt; 201703L
#include &lt;memory&gt;
#endif

std::string format(const char *fmt, ...)
{
    char buf[256];

    va_list args;
    va_start(args, fmt);
    const auto r = std::vsnprintf(buf, sizeof buf, fmt, args);
    va_end(args);

    if (r &lt; 0)
        // conversion failed
        return {};

    const size_t len = r;
    if (len &lt; sizeof buf)
        // we fit in the buffer
        return { buf, len };

#if __cplusplus &gt;= 201703L
    // C++17: Create a string and write to its underlying array
    std::string s(len, '\0');
    va_start(args, fmt);
    std::vsnprintf(s.data(), len+1, fmt, args);
    va_end(args);

    return s;
#else
    // C++11 or C++14: We need to allocate scratch memory
    auto vbuf = std::unique_ptr&lt;char[]&gt;(new char[len+1]);
    va_start(args, fmt);
    std::vsnprintf(vbuf.get(), len+1, fmt, args);
    va_end(args);

    return { vbuf.get(), len };
#endif
}




// Test program

#include &lt;iostream&gt;
int main()
{
    std::clog &lt;&lt; "'" &lt;&lt; format("a")
              &lt;&lt; "'" &lt;&lt; std::endl;
    std::clog &lt;&lt; "'" &lt;&lt; format("%#x", 1337)
              &lt;&lt; "'" &lt;&lt; std::endl;
    std::clog &lt;&lt; "'" &lt;&lt; format("--%c--", 0) // an embedded NUL
              &lt;&lt; "'" &lt;&lt; std::endl;
    std::clog &lt;&lt; "'" &lt;&lt; format("%300s++%6.2f", "**", 0.0).substr(300)
              &lt;&lt; "'" &lt;&lt; std::endl;
}

void provoke_warnings()
{
    // warning: zero-length gnu_printf format string
    // [-Wformat-zero-length]
    std::clog &lt;&lt; "'" &lt;&lt; format("") &lt;&lt; "'" &lt;&lt; std::endl;

    // warning: format ‘%c’ expects argument of type ‘int’, but
    // argument 2 has type ‘const char*’ [-Wformat=]
    std::clog &lt;&lt; "'" &lt;&lt; format("%c", "bar") &lt;&lt; "'" &lt;&lt; std::endl;
}


I've compiled the code with both C++17 and C++11 compilers, and verified them both under Valgrind using this test program.

I'd welcome any comments on the code itself or on my testing.
I agree with you that 'printf-style' formatting was by many aspects better than C++'s manipulators: more concise, more varied, etc. That said, I feel like it's a step backwards if we port them into C++ without upgrading the C logic behind it. In my opinion, your code fails to do that for two reasons: lack of automatic memory management, and lack of type safety.

About type safety, I believe we now must use variadic templates when dealing with an unknown number of arguments. Variadic macros have too many shortcomings for us to continue using them. With variadic templates, we're able to verify if format directives and arguments match in number and types in a more reliable way (although I agree that when you can rely on a format chain the risk is manageable even with C-like macros).

About memory management: I believe we must avoid explicit memory management whenever we can. std::stringstream is a good way to do that when one wants to build a string progressively from heterogeneous arguments.

So here's an embryo of what I would consider more modern C++:

#include &lt;string&gt;
#include &lt;sstream&gt;
#include &lt;iostream&gt;
#include &lt;type_traits&gt;
#include &lt;exception&gt;

// base case of recursion, no more arguments
void format_impl(std::stringstream&amp; ss, const char* format) { 
    while (*format) {
        if (*format == '%' &amp;&amp; *++format != '%') // %% == % (not a format directive)
            throw std::invalid_argument("not enough arguments !\n");
        ss &lt;&lt; *format++;
    }
}

template &lt;typename Arg, typename... Args&gt;
void format_impl(std::stringstream&amp; ss, const char* format, Arg arg, Args... args) {
    while (*format) {
        if (*format == '%' &amp;&amp; *++format != '%') {
            auto current_format_qualifier = *format;
            switch(current_format_qualifier) {
                case 'd' : if (!std::is_integral&lt;Arg&gt;()) throw std::invalid_argument("%d introduces integral argument");
                // etc.
            }
            // it's true you'd have to handle many more format qualifiers, but on a safer basis
            ss &lt;&lt; arg; // arg type is deduced
            return format_impl(ss, ++format, args...); // one arg less
        }
        ss &lt;&lt; *format++;
        } // the format string is exhausted and we still have args : throw
    throw std::invalid_argument("Too many arguments\n");
}

template &lt;typename... Args&gt;
std::string format(const char* fmt, Args... args) {
    std::stringstream ss;
    format_impl(ss, fmt, args...);
    return ss.str();
}

int main() {
    auto display = format("My name is %s and I'm %d year old", "John", 22);
    std::cout &lt;&lt; display;
}