Boost Format is a very versatile and easy to use formatting library. If you’re using Boost, then there is no point in using other formatting libraries for formatting. For most uses, boost::format
and boost::wformat
are perfectly suitable.
Boost Format is not as fast a sprintf and that family of formatters, but it is type safe and robust. It uses streams to construct the output, so there is no chance of buffer overflow and other nasal demons coming out of your nose. According to that table, Boost Spirit Karma is even faster. That’s worthy a blog post in itself, but this is about boost::format
.
This is a continuation of the first boost format post, with more images and descriptions.
Contents
I’ve used boost::format in many projects and it has an easy syntax once you get to know it. It has two types of formatting rules, one is printf-like formatting and the other is positional arguments (like formatting in C#). I’ve never had a reason to use positional notation, however they are very similar to the print-like formatting rules and the advantage is that you can switch the order of arguments during runtime (think i18n and language translation support).
Formatting floating point numbers is one of the most used features of string formatting. There aren’t many other ways of outputting floating point numbers than use formatting of some sort.
const double almostpi = 22.0 / 7.0; // Printf printf("Pi is %fn", almostpi); // Boost format with printf syntax boost::format printf_formatting("Pi is %fn"); std::cout << printf_formatting % almostpi; // Boost format with positional syntax boost::format position_formatting("Pi is %1%n"); std::cout << position_formatting % almostpi; // Output: // Pi is 3.142857 // Pi is 3.142857 // Pi is 3.14286
The archaic method printf
is just here as a comparison. It should not be used in new code as this family of formatters is regarded unsafe.
Lets see what is happening here. The formatting rule %f
tells the printf method to format a floating point number using default rules, which is about 6 decimal points.
There is really nothing more magic than that. The rest of the options are just variations of the same. Replace a specific code with some contents.
The same diagram for boost::format
with printf-style formatting rules.
Formatting large numbers are similar to those small numbers.
const double almostpi = 22.0 / 7.0; const double distancetosun = 149600000000.0; // meters const double earthorbitlength = 2 * almostpi * distancetosun; // Printf printf("Earth orbit distance is %f metersn", earthorbitlength); // Boost format with printf syntax boost::format printf_formatting("Earth orbit distance is %f metersn"); std::cout << printf_formatting % earthorbitlength; // Boost format with positional syntax boost::format position_formatting("Earth orbit distance is %1% metersn"); std::cout << position_formatting % earthorbitlength; // Output: // Earth orbit distance is 940342857142.857180 meters // Earth orbit distance is 940342857142.857180 meters // Earth orbit distance is 9.40343e+011 meters
The default precision is still 6 decimal points, however the %1%
formatter decided scientific notation was a better fit. More on that later.
The format specifier for old style print is “%[flags][width][.precision][length]specifier “.
The format specifier for boost format positional notation is: “%[N$][flags][width][.precision]type-char
“.
Both formats are very similar, and there are only a few actual differences between those two specifiers.
For most purposes, the default settings are sufficient when you force the type of the format, except when using the default %1%
type. It’ll switch over to scientific notation around 6-7 characters length.
When formatting large numbers, the number will never be truncated. That decision is a trade-off between wanting to show the correct number or use the correct formatting. It’s easier to see the formatting is off, than to figure out the number is missing some integers in one or other end in some printout.
// Printf printf("Pi is '%10.2f'n", almostpi); // Boost format with printf syntax boost::format printf_formatting("Pi is '%10.2f'n"); std::cout << printf_formatting % almostpi; // Boost format with positional syntax boost::format position_formatting("Pi is '%1$10.2f'n"); std::cout << position_formatting % almostpi; // Output: // Pi is ' 3.14' // Pi is ' 3.14' // Pi is ' 3.14'
Numbers are not truncated when they overflow the width
.
// Printf printf("Earth orbit distance is '%5.2f' metersn", earthorbitlength); // Boost format with printf syntax boost::format printf_formatting("Earth orbit distance is '%5.2f' metersn"); std::cout << printf_formatting % earthorbitlength; // Boost format with positional notation boost::format position_formatting("Earth orbit distance is '%1$5.2f' metersn"); std::cout << position_formatting % earthorbitlength; // Output: // Earth orbit distance is '940342857142.86' meters // Earth orbit distance is '940342857142.86' meters // Earth orbit distance is '940342857142.86' meters
At some point, the positional notation formatter will switch to scientific notation when you’re not using any type specifiers. Here is a small test demonstrating when it will switch from floating point representation and over to scientific notatation.
boost::format scinotation("Do we use scientific? '%1%'n"); for (size_t n=0; n<15; ++n) { double value = 1 * pow(10, n) + 0.5; std::cout << scinotation % value; } // Output: // Do we use scientific? '1.5' // Do we use scientific? '10.5' // Do we use scientific? '100.5' // Do we use scientific? '1000.5' // Do we use scientific? '10000.5' // Do we use scientific? '100001' // Do we use scientific? '1e+006' // Do we use scientific? '1e+007' // Do we use scientific? '1e+008' // Do we use scientific? '1e+009' // Do we use scientific? '1e+010' // Do we use scientific? '1e+011' // Do we use scientific? '1e+012' // Do we use scientific? '1e+013' // Do we use scientific? '1e+014'
Here is a brief example on how to use the positional notation for translations with wformat.
// Language string input parameters // 1 - Title // 2 - Given name // 3 - Family name std::wstring english = L"Hello, %1% %3%, %2%n"; std::wstring norwegian = L"Hei %2% %3%n"; std::wstring russian = L"здравствуйте %2% %3%"; std::wstring title = L"Mr."; std::wstring name = L"John"; std::wstring family = L"Doe"; std::wcout << "English: " << boost::wformat(english) % title % name % family; std::wcout << "Norwegian: " << boost::wformat(norwegian) % title % name % family; std::wcout << "Russian: " << boost::wformat(russian) % title % name % family; // Output: // English: Hello, Mr.Doe, John // Norwegian: Hei John Doe // Russian: здравствуйте John Doe
The idea is that for each string requiring formatting, there must be a predefined set of keys
. In this example, all three (title, name, family name) is given into the boost::wformat object, but only the necessary parameters are used. This is (almost) impossible with the printf-family.
With positional notation, it’s fully supported to use a different order than the order of parameters.
Read also: Boost format examples: dates, time, alignment, loops.
String format
This is a very basic example on how easy it is to corrupt data on the stack with printf / sprintf. Due to how different compilers and runtimes do things, the result is undefined. It might format your computer, but most times this line will corrupt the stack space and hopefully crash with a meaningful message. Visual Studio will usually detect stack space corruption during debug, since it uses canaries around variables which is set to a certain value (also known as no-mans-land). If the value is not as expected, an assert will trigger and notify the debugger there are problems.
char important[10] = {0}; // Initialize to nulls char characters[10] = {0}; // Initialize to nulls char important2[10] = {0}; // Initialize to nulls sprintf(characters, "Overflow '%5.2f'", almostpi); // At this point, the stack space should be corrupted around 'characters'
With Visual Studio, you’ll usually get this warning if you’re in debug mode.
Professional Software Developer, doing mostly C++. Connect with Kent on Twitter.