March 2015

Volume 30 Number 3


Windows with C++ - Using Printf with Modern C++

By Kenny Kerr | March 2015

Kenny KerrWhat would it take to modernize printf? That might seem like an odd question to many developers who believe that C++ already provides a modern replacement for printf. While the claim to fame of the C++ Standard Library is undoubtedly the excellent Standard Template Library (STL), it also includes a stream-based input/­output library that bears no resemblance to STL and embodies none of its principles related to efficiency.

“Generic programming is an approach to programming that focuses on design algorithms and data structures so that they work in the most general setting without loss of efficiency,” according to Alexander Stepanov and Daniel Rose, in the book, “From Mathematics to Generic Programming” (Addison-Wesley Professional, 2015).

To be honest, neither printf nor cout is in any way representative of modern C++. The printf function is an example of a variadic function and one of the few good uses of this somewhat brittle feature inherited from the C programming language. Variadic functions predate variadic templates. The latter offer a truly modern and robust facility for dealing with a variable number of types or arguments. In contrast, cout doesn’t employ variadic anything, but instead relies so heavily on virtual function calls that the compiler isn’t able to do much to optimize its performance. Indeed, the evolution of CPU design has favored printf while doing little to improve the performance of the polymorphic approach of cout. Therefore, if you want performance and efficiency, printf is a better choice. It also produces code that’s more concise. Here’s an example:

#include <stdio.h>
int main()
{
  printf("%f\n", 123.456);
}

The %f conversion specifier tells printf to expect a floating-point number and convert it to decimal notation. The \n is just an ordinary newline character that may be expanded to include a carriage return, depending on the destination. The floating-point conversion assumes a precision of 6, referring to the number of digits that will appear after the decimal point. Thus, this example will print the following characters, followed by a new line:

123.456000

Achieving the same end with cout seems relatively straight­forward at first:

#include <iostream>
int main()
{
  std::cout << 123.456 << std::endl;
}

Here, cout relies on operator overloading to direct or send the floating-point number to the output stream. I don’t like the abuse of operator overloading in this way, but I admit it’s a matter of personal style. Finally, endl concludes by inserting a new line into the output stream. However, this isn’t quite the same as the printf example and produces output with a different decimal precision:

123.456

This leads to an obvious question: How can I change the precision for the respective abstractions? Well, if I only want two digits following the decimal point, I can simply specify this as part of the printf float-point conversion specifier:

printf("%.2f\n", 123.456);

Now printf will round the number to produce the following result:

123.46

To get the same effect with cout requires a bit more typing:

#include <iomanip> // Needed for setprecision
std::cout << std::fixed << std::setprecision(2)
          << 123.456 << std::endl;

Even if you don’t mind the verbosity of all of this and rather enjoy the flexibility or expressiveness, keep in mind that this abstraction comes at a cost. First, the fixed and setprecision manipulators are stateful, meaning their effect persists until they’re reversed or reset. By contrast, the printf conversion specifier includes everything required for that single conversion, without affecting any other code. The other cost may not matter for most output, but the day might come when you notice that everyone else’s programs can output many times faster than yours can. Apart from the overhead from virtual function calls, endl also gives you more than you might have bargained for. Not only does it send a new line to the output, but it also causes the underlying stream to flush its output. When writing any kind of I/O, whether to the console, a file on disk, a network connection, or even a graphics pipeline, flushing is usually very costly, and repeated flushes will undoubtedly hurt performance.

Now that I’ve explored and contrasted printf and cout a little, it’s time to return to the original question: What would it take to modernize printf? Surely, with the advent of modern C++, as exemplified by C++11 and beyond, I can improve the productivity and reliability of printf without sacrificing performance. Another somewhat unrelated member of the C++ Standard Library is the language’s official string class. Although this class has also been maligned over the years, it does offer excellent performance. While not without fault, it provides a very useful way to handle strings in C++. Therefore, any modernization of printf really ought to play nicely with string and wstring. Let’s see what can be done. First, let me address what I consider to be the most vexing problem of printf:

std::string value = "Hello";
printf("%s\n", value);

This really ought to work, but as I’m sure you can plainly see, instead it will result in what is lovingly known as “undefined behavior.” As you know, printf is all about text and the C++ string class is the premier manifestation of text in the C++ language. What I need to do is wrap printf in such a way that this just works. I don’t want to have to repeatedly pluck out the string’s null-terminated character array as follows:

printf("%s\n", value.c_str());

This is just tedious, so I’m going to fix it by wrapping printf. Traditionally, this has involved writing another variadic function. Perhaps something like this:

void Print(char const * const format, ...)
{
  va_list args;
  va_start(args, format);
  vprintf(format, args);
  va_end(args);
}

Unfortunately, this gains me nothing. It might be useful to wrap some variant of printf in order to write to some other buffer, but in this case, I’ve gained nothing of value. I don’t want to go back to C-style variadic functions. Instead, I want to look forward and embrace modern C++. Fortunately, thanks to the C++11 variadic templates, I’ll never have to write another variadic function in my life. Rather than wrapping the printf function in another variadic function, I can instead wrap it in a variadic template:

template <typename ... Args>
void Print(char const * const format,
           Args const & ... args) noexcept
{
  printf(format, args ...);
}

At first, it might not seem that I’ve gained much. If I were to call the Print function like this:

Print("%d %d\n", 123, 456);

it would cause the args parameter pack, consisting of 123 and 456, to expand inside the body of the variadic template as if I had simply written this:

      

printf("%d %d\n", 123, 456);

So what have I gained? Sure, I’m calling printf rather than vprintf and I don’t need to manage a va_list and the associated stack-­twiddling macros, but I’m still merely forwarding arguments. Don’t overlook the simplicity of this solution, however. Again, the compiler will unpack the function template’s arguments as if I had simply called printf directly, which means there’s no overhead in wrapping printf in this way. It also means this is still first-class C++ and I can employ the language’s powerful metaprogramming techniques to inject any requisite code—and in a completely generic way. Rather than simply expanding the args parameter pack, I can wrap each argument to add any adjustment needed by printf. Consider this simple function template:

template <typename T>
T Argument(T value) noexcept
{
  return value;
}

It doesn’t appear to do much and indeed it doesn’t, but I can now expand the parameter pack to wrap each argument in one of these functions as follows:

template <typename ... Args>
void Print(char const * const format,
           Args const & ... args) noexcept
{
  printf(format, Argument(args) ...);
}

I can still call the Print function in the same way:

Print("%d %d\n", 123, 456);

But it now effectively produces the following expansion:

printf("%d %d\n", Argument(123), Argument(456));

This is very interesting. Sure, it makes no difference for these integer arguments, but I can now overload the Argument function to handle C++ string classes:

template <typename T>
T const * Argument(std::basic_string<T> const & value) noexcept
{
  return value.c_str();
}

Then I can simply call the Print function with some strings:

int main()
{
  std::string const hello = "Hello";
  std::wstring const world = L"World";
  Print("%d %s %ls\n", 123, hello, world);
}

The compiler will effectively expand the inner printf function as follows:

printf("%d %s %ls\n",
  Argument(123), Argument(hello), Argument(world));

This ensures that each string’s null-terminated character array is provided to printf and produces a completely well-defined behavior:

123 Hello World

Along with the Print function template, I also use a number of overloads for unformatted output. This tends to be safer and prevents printf from accidentally misinterpreting arbitrary strings as containing conversion specifiers. Figure 1 lists these functions.

Figure 1 Printing Unformatted Output

inline void Print(char const * const value) noexcept
{
  Print("%s", value);
}
inline void Print(wchar_t const * const value) noexcept
{
  Print("%ls", value);
}
template <typename T>
void Print(std::basic_string<T> const & value) noexcept
{
  Print(value.c_str());
}

The first two overloads simply format ordinary and wide-character arrays, respectively. The final function template forwards to the appropriate overload, depending on whether a string or wstring is provided as an argument. Given these functions, I can safely print some conversion specifiers literally, as follows:

Print("%d %s %ls\n");

That takes care of my most common gripe with printf by handling string output safely and transparently. What about formatting strings themselves? The C++ Standard Library provides different variants of printf for writing to character string buffers. Of these, I find snprintf and swprintf the most effective. These two functions handle character and wide-character output, respectively. They allow you to specify the maximum number of characters that may be written and return a value that can be used to calculate how much space is needed should the original buffer not be large enough. Still, on their own they’re error-prone and quite tedious to use. Time for some modern C++.

While C doesn’t support function overloading, it’s far more convenient to use overloading in C++ and this opens the door for generic programming, so I’ll start by wrapping both snprintf and swprintf as functions called StringPrint. I’ll also use variadic function templates so I can take advantage of the safe argument expansion I previously used for the Print function. Figure 2 provides the code for both functions. These functions also assert that the result is not -1, which is what the underlying functions return when there’s some recoverable problem parsing the format string. I use an assertion because I just assume this is a bug and should be fixed prior to shipping production code. You might want to replace this with an exception, but keep in mind there’s no bulletproof way of turning all errors into exceptions as it’s still possible to pass invalid arguments that will lead to undefined behavior. Modern C++ is not idiot-proof C++.

Figure 2 Low-Level String Formatting Functions

template <typename ... Args>
int StringPrint(char * const buffer,
                size_t const bufferCount,
                char const * const format,
                Args const & ... args) noexcept
{
  int const result = snprintf(buffer,
                              bufferCount,
                              format,
                              Argument(args) ...);
  ASSERT(-1 != result);
  return result;
}
template <typename ... Args>
int StringPrint(wchar_t * const buffer,
                size_t const bufferCount,
                wchar_t const * const format,
                Args const & ... args) noexcept
{
  int const result = swprintf(buffer,
                              bufferCount,
                              format,
                              Argument(args) ...);
  ASSERT(-1 != result);
  return result;
}

The StringPrint functions provide a generic way of dealing with string formatting. Now I can focus on the specifics of the string class, and this mostly involves memory management. I’d like to write code like this:

std::string result;
Format(result, "%d %s %ls", 123, hello, world);
ASSERT("123 Hello World" == result);

There’s no visible buffer management. I don’t have to figure out how large a buffer to allocate. I simply ask the Format function to logically assign the formatted output to the string object. As usual, Format can be a function template, specifically a variadic template:

template <typename T, typename ... Args>
void Format(std::basic_string<T> & buffer,
            T const * const format,
            Args const & ... args)
{
}

There are a variety of ways to implement this function. Some experimentation and a good dose of profiling go a long way. A simple but naïve approach is to assume the string is either empty or too small to contain the formatted output. In that case, I’d start by determining the required size with StringPrint, resize the buffer to match, and then call StringPrint again with the properly allocated buffer. Something like this:

size_t const size = StringPrint(nullptr, 0, format, args ...);
buffer.resize(size);
StringPrint(&buffer[0], buffer.size() + 1, format, args ...);

The + 1 is required because both snprintf and swprintf assume the reported buffer size includes space for the null terminator. This works well enough, but it should be obvious that I’m leaving performance on the table. A much faster approach in most cases is to assume the string is large enough to contain the formatted output and resize only if necessary. This almost reverses the preceding code but is quite safe. I begin by attempting to format the string directly into the buffer:

size_t const size = StringPrint(&buffer[0],
                                buffer.size() + 1,
                                format,
                                args ...);

If the string is empty to begin with or just not large enough, the resulting size will be greater than the size of the string and I’ll know to resize the string before calling StringPrint again:

if (size > buffer.size())
{
  buffer.resize(size);
  StringPrint(&buffer[0], buffer.size() + 1, format, args ...);
}

If the resulting size is less than the size of the string, I’ll know that the format succeeded, but the buffer needs to be trimmed to match:

else if (size < buffer.size())
{
  buffer.resize(size);
}

Finally, if the sizes match there’s nothing to do and the Format function can simply return. The complete Format function template can be found in Figure 3. If you’re familiar with the string class, you might recall that it also reports its capacity and you might be tempted to set the string’s size to match its capacity prior to calling StringPrint the first time, thinking that this might improve your odds of formatting the string correctly the first time. The question is whether a string object can be resized faster than printf can parse its format string and calculate the required buffer size. Based on my informal tests, the answer is: it depends. You see, resizing a string to match its capacity is more than simply changing the reported size. Any additional characters must be cleared and this takes time. Whether this takes more time than it takes printf to parse its format string depends on how many characters need to be cleared and how complex the formatting happens to be. I use an even faster algorithm for high-­volume output, but I’ve found that the Format function in Figure 3 provides good performance for most scenarios.

Figure 3 Formatting Strings

template <typename T, typename ... Args>
void Format(std::basic_string<T> & buffer,
            T const * const format,
            Args const & ... args)
{
  size_t const size = StringPrint(&buffer[0],
                                  buffer.size() + 1,
                                  format,
                                  args ...);
  if (size > buffer.size())
  {
    buffer.resize(size);
    StringPrint(&buffer[0], buffer.size() + 1, format, args ...);
  }
  else if (size < buffer.size())
  {
    buffer.resize(size);
  }
}

With this Format function in hand, it also becomes very easy to write various helper functions for common string-formatting operations. Perhaps you need to convert a wide-character string to an ordinary string:

inline std::string ToString(wchar_t const * value)
{
  std::string result;
  Format(result, "%ls", value);
  return result;
}
ASSERT("hello" == ToString(L"hello"));

Perhaps you need to format floating-point numbers:

inline std::string ToString(double const value,
                            unsigned const precision = 6)
{
  std::string result;
  Format(result, "%.*f", precision, value);
  return result;
}
ASSERT("123.46" == ToString(123.456, 2));

For the performance obsessed, such specialized conversion functions are also quite easy to optimize further because the required buffer sizes are somewhat predictable, but I’ll leave that for you to explore on your own.

This is just a handful of useful functions from my modern C++ output library. I hope they’ve given you some inspiration for how to use modern C++ to update some old-school C and C++ programming techniques. By the way, my output library defines the Argument functions, as well as the low-level StringPrint functions in a nested Internal namespace. This tends to keep the library nice and simple to discover, but you can arrange your implementation however you wish.


Kenny Kerr is a computer programmer based in Canada, as well as an author for Pluralsight and a Microsoft MVP. He blogs at kennykerr.ca and you can follow him on Twitter at twitter.com/kennykerr.