Software Development

Using The New Formatting And Printing Capabilities In C++20 And C++23

C++20 And C++23 Formatting And Printing Graphic
Written by John Woolsey

Last Updated: August 22, 2023
Originally Published: May 19, 2023

Skill Level: Intermediate

Table Of Contents

Introduction

This tutorial will show you how to use the new string formatting and printing capabilities available in the new C++20 and C++23 language specifications.

A basic understanding of C/C++ programming is expected.

The resources created for this tutorial are available on GitHub for your reference.

This tutorial is provided as a free service to our valued readers. Please help us continue this endeavor by considering a GitHub sponsorship or a PayPal donation.

What Is Needed

  • Personal Computer Or Workstation
  • C++ Compiler

Background Information

The C++20 specification defines a new text formatting library, named <format>, that “offers a safe and extensible alternative to the printf family of functions”. It includes a new std::format() function that formats text in a similar fashion to the C based printf() function and is based on Python’s string formatting specification.

The C++23 specification extends the text formatting work in C++20 to enable the printing of formatted text directly to the screen with the std::print() and std::println() functions contained within the new <print> library.

Most of the major compiler vendors have already been incorporating C++20 features into their compilers. It is my understanding that the <format> library in general, and the std::format() function in particular, is already available in the MSVC 16.10 (with the /std:c++20 switch), Clang 14 (with the -std=c++20 option), and GCC 13.1 (with the -std=c++20 option) compilers.

Since I am on a Mac, Apple’s version of Clang does not yet support the std::format() function at this time. However, the latest major GNU Compiler Collection (GCC) version, 13.1, was released on April 26th, 2023 and does provide support for the <format> library’s std::format() function. I installed it via Homebrew and that is the compiler I will be using on my system.

If you need assistance with your particular setup, post a question in the comments section below and I, or someone else, can try to help you.

Formatted Printing With Standard C

Before we begin, since we will be attempting to use fairly new features of recent C++ specifications within this tutorial, it is recommended that you install the latest version(s) of your favorite C++ compiler(s).

Let’s start with a simple C++ program that uses the classic C style formatted printing with the printf() function. Since C++ is a superset of the C language, we still have access to that function in our program.

Create a new project directory, named formatted_output, and then go into that directory. Open your favorite code editor and create the formatted_output.h header and formatted_output.cpp implementation files with the contents shown below.

formatted_output.h (header file):

#ifndef FORMATTED_OUTPUT_H
#define FORMATTED_OUTPUT_H

#include <climits>
#include <cstdio>
#include <string>
#include <string_view>

#ifndef M_PI
   #define M_PI 3.14159265358979323846
#endif

void c_standard(std::string_view string_value,
                std::string_view int_name, int int_value,
                std::string_view float_name, float float_value);

#endif

formatted_output.cpp (implementation file):

#include "formatted_output.h"

int main() {
   constexpr std::string_view world = "World";
   constexpr std::string_view int_max_name = "INT_MAX";
   constexpr int int_max_value = INT_MAX;
   constexpr std::string_view pi_name = "Pi";
   constexpr float pi_value = M_PI;

   c_standard(world, int_max_name, int_max_value, pi_name, pi_value);
}

void c_standard(std::string_view string_value,
                std::string_view int_name, int int_value,
                std::string_view float_name, float float_value) {
   printf("C standard formatted printing with printf():\n");
   printf("Hello, %s!\n", std::string(string_value).c_str());
   printf("%7s = %10d\n", std::string(int_name).c_str(), int_value);
   printf("%7s = %10.5f\n\n", std::string(float_name).c_str(), float_value);
}

The header file begins by including the libraries we need for the program. <climits> includes the INT_MAX macro, <cstdio> includes the printf() function, and <string> and <string_view> include the std::string and std::string_view datatypes respectively.

Some, but not all, compilers define the M_PI macro as the value of the Pi mathematical constant. Lines 9-11 just define it if it is not already defined.

Lines 13-15 declare our c_standard() function prototype that will be defined in the implementation file.

The main() function in the implementation file begins by defining various example variables (lines 4-8) that we will be using throughout our program. Those variables are then passed into the c_standard() demonstration function that will utilize the C based printf() function to format and print those variables to the screen.

I am sure most of you are familiar with the printf() function notation, but let me offer a quick synopsis of what each line does.

Line 16 prints a simple string to the screen. I am also using this line to communicate the formatted printing method that is being utilized by this particular demonstration function.

Line 17 prints a string using simple string substitution. However, since the printf() function expects the classic C-string datatype, we need to convert the std::string_view type to a C-string before it can be used by the printf() function. To do this, we first cast the std::string_view type to a std::string type and then apply the std::string‘s c_str() method to make the conversion.

Line 18 prints a compound string using string and integer substitution and formatting. The %7s specifier specifies a string field with a width of 7 characters and %10d specifies an integer field with a width of 10.

Line 19 prints a compound string using string and floating point substitution and formatting. %10.5f specifies a floating point field with a width of 10 using 5 decimal places of precision. I also added an additional blank line (\n) to the end of this statement.

For more information about the printf() function’s formatting specifications, see the printf() function’s documentation.

Since we will cover multiple demonstration examples of formatted printing functionality in the following sections, I separated them into their own distinct functions. Therefore, the c_standard() function, this first example, is the only function I am currently calling within the main() function.

If there is something that needs further explanation, please let me know in the comment section and I will try to answer your question.

Save your code when you are done editing and then compile the program with your favorite C++ compiler. I used the following to compile my program with GCC on the command line. The -Wall and -Wextra options were used to report additional warnings of potential code issues found by the compiler.

$ g++-13 -Wall -Wextra -o formatted_output formatted_output.cpp

Next, run the program

$ ./formatted_output

and you should see the following printed to your screen. Note, you may see a different value for INT_MAX as it depends on both your compiler and the system architecture being used.

C standard formatted printing with printf():
Hello, World!
INT_MAX = 2147483647
     Pi =    3.14159

Formatted Printing With Standard C++

While many people like to use the C based printf() function in their C++ code, many others argue that it is unsafe to do so. The use of std::cout, std::endl, and other stream based objects and functions are the recommended method for printing to the screen in C++. This section demonstrates how to use that preferred method to print the same formatted output we printed in the previous section that used printf().

Update the formatted_output.h header file to add the extra libraries

#include <iomanip>
#include <iostream>

and the cpp_standard() function prototype.

void cpp_standard(std::string_view string_value,
                  std::string_view int_name, int int_value,
                  std::string_view float_name, float float_value);

The <iostream> library adds the std::cout, std::endl, and std::fixed stream objects while the <iomanip> library adds the std::setprecision() and std::setw() functions that will be used in the cpp_standard() example function.

Update the formatted_output.cpp implementation file to add the cpp_standard() function

void cpp_standard(std::string_view string_value,
                  std::string_view int_name, int int_value,
                  std::string_view float_name, float float_value) {
   std::cout << "C++ standard printing with std::cout and std::endl and formatting with std::fixed, std::setprecision(), and std::setw():\n";
   std::cout << "Hello, " << string_value << "!\n";
   std::cout << std::setw(7) << int_name << " = " << std::setw(10) << int_value << "\n";
   std::cout << std::fixed << std::setprecision(5);
   std::cout << std::setw(7) << float_name << " = " << std::setw(10) << float_value << "\n" << std::endl;
}

and then add it to the main() function after the c_standard() function call.

cpp_standard(world, int_max_name, int_max_value, pi_name, pi_value);

Within the cpp_standard() example function, the std::setw() function is added to the output stream whenever we want to define the character width to use for the next item in the stream. The std::fixed object and std::precision() function are used to fix the number of decimal digits used to represent a floating point number.

Save, compile, and run the updated program and you should see the following output.

C standard formatted printing with printf():
Hello, World!
INT_MAX = 2147483647
     Pi =    3.14159

C++ standard printing with std::cout and std::endl and formatting with std::fixed, std::setprecision(), and std::setw():
Hello, World!
INT_MAX = 2147483647
     Pi =    3.14159

The three formatted lines after the method descriptions should be identical between the C standard and C++ standard sections.

When comparing the cpp_standard() function against the c_standard() function, it becomes apparent that it takes quite a bit more code to accomplish the same string formatting in “standard” C++ as it does in “standard” C. In addition, some folks also find it even more cumbersome to use. These are just some of the reasons why they continue using the old-time classic printf() function.

Formatted Printing With C++20 <format> Library

The designers of the C++20 specification tried to alleviate those concerns mentioned previously by implementing a new text formatting library, named <format>. It contains the new std::format() function that uses string interpolation that is not only similar to C’s printf() function but also to the text formatting methods implemented by other programming languages as well.

Update the header file to include the <format> library by adding the following just below the existing #include statements.

#ifdef __has_include
   #if __has_include(<format>)
      #include <format>
   #else
      #warning "The <format> library is not available."
   #endif
#else
   #warning "The __has_include check is not available."
#endif

The __has_include macro (added in C++17) checks if a library or other source file is available. If it is, we include it. Here, we are checking the availability of the <format> library before including it in our program. I also implemented compiler warnings to let the user know if the __has_include macro or <format> library is not available for use. Note, the #warning directive has been implemented in various compilers for quite some time, but was not officially part of the standard specification until C++23.

You will also need to add the following cpp_format() function prototype to the header file.

void cpp_format(std::string_view string_value,
                std::string_view int_name, int int_value,
                std::string_view float_name, float float_value);

Update the implementation file to add the cpp_format() example function

void cpp_format(std::string_view string_value,
                std::string_view int_name, int int_value,
                std::string_view float_name, float float_value) {
   #ifdef __cpp_lib_format
      std::cout << "C++ standard printing with std::cout and std::endl and <format> based formatting with std::format():\n";
      std::cout << std::format("Hello, {}!\n", string_value);
      std::cout << std::format("{0:>7s} = {1:10d}\n", int_name, int_value);
      std::cout << std::format("{0:>7s} = {1:10.5f}\n", float_name, float_value) << std::endl;
   #else
      std::cout << "C++ formatting with <format> based std::format() is not yet available; expected in C++20.\n" << std::endl;
   #endif
}

and then add a call to it at the end of the main() function.

The __cpp_lib_format feature test macro will only be defined if the <format> library was included and its features are available for use. Here, we are checking if the macro is defined, if it is, we utilize the std::format() function, if it is not, we alert the user to the function not yet being available.

While the specifier syntax of the std::format() function may look different than the syntax used by the printf() function, the formatting options are actually quite similar. Instead of using the % placeholder notation, we are using {} instead. If the {} placeholder is empty, as in line 6, basic substitution with implicit positional arguments is used. That means the placeholders in the formatted string are replaced with the std::format() function’s arguments, after the format string, in the same order as they are listed.

If the {} placeholder contains a colon (:), the number to the left of the colon refers to the explicit positional argument (which may be empty to use implicit positional arguments), and the characters to the right of the colon specify the formatting of the text to use for the placeholder’s field. In line 8 for instance, the {0:>7s} placeholder specifies to use the 1st argument, beginning with 0, right justify the field, use a field width of 7 characters, and the field’s type is a string. The {1:10.5f} placeholder specifies to use the 2nd argument, use a field width of 10 characters, use 5 decimal digits of precision, and the field’s type is a floating point number. Note, specifying the justification was not required when printing strings with printf() as the default is already right justified.

For more information about the std::format() function’s capabilities, see the function’s documentation.

Save your program files when you are done updating them.

It is likely that your compiler conforms to a different, earlier, specification of the C++ language by default. For instance, as of GCC 13.1, the default specification is C++17. In order to use the language and library features of a newer specification, we need to tell the compiler which one to use. The MSVC compiler requires the /std:c++20 switch while the Clang and GCC compilers use the -std=c++20 option. I will be using the following to run my updated program with GCC on my Mac.

$ g++-13 -Wall -Wextra -std=c++20 -o formatted_output formatted_output.cpp

Compile and run the program and you should see the same formatted output for this example function as we have seen in the previous sections.

Although usage of the <format> library is still experimental at this time, at least in GCC’s case, the basics all seem to be in place. Keep in mind that some modifications may be made in the future.

Formatted Printing With C++23 <print> Library

With the above std::format() function, we now have a more concise syntax to print our formatted text to the screen, but we still have to use the std::cout stream object to print the formatted text to the screen. The C++23 specification resolves this last “issue” by adding the <print> library that contains the std::print() and std::println() functions. These new functions allow you to print formatted text directly to the screen with a single function call as we were able to do with the classic C printf() function.

Update the header file to add the <print> library by replacing the conditional include section with the following that adds the new library.

#ifdef __has_include
   #if __has_include(<format>)
      #include <format>
   #else
      #warning "The <format> library is not available."
   #endif
   #if __has_include(<print>)
      #include <print>
   #else
      #warning "The <print> library is not available."
   #endif
#else
   #warning "The __has_include check is not available."
#endif

Then add the new cpp_print() function prototype to the header file.

void cpp_print(std::string_view string_value,
               std::string_view int_name, int int_value,
               std::string_view float_name, float float_value);

Update the implementation file to add the cpp_print() example function

void cpp_print(std::string_view string_value,
               std::string_view int_name, int int_value,
               std::string_view float_name, float float_value) {
   #ifdef __cpp_lib_print
      std::println("C++ <print> based formatted printing with std::print() and std::println():");
      std::print("Hello, {}!\n", string_value);
      std::println("{0:>7s} = {1:10d}", int_name, int_value);
      std::println("{0:>7s} = {1:10.5f}\n", float_name, float_value);
   #else
      std::cout << "C++ formatted printing with <print> based std::print() and std::println() is not yet available; expected in C++23.\n" << std::endl;
   #endif
}

and then add its call to the main() function.

As you can probably see, the std::println() function replaces the usage of both the std::cout and std::endl stream objects along with the std::format() function while still using the same formatting syntax. The std::println() function is equivalent to the std::print() function terminated by an additional new line (\n). I am not positive that the new std::println() function will also flush the output stream, as std::endl does, but I assume that will be the case.

We are also using the new __cpp_lib_print feature test macro in this cpp_print() demonstration function to test for inclusion of the <print> library.

Save and compile the updated program, but this time, specify the C++23 specification. You will probably see warnings about the <print> library not being available and unused parameters within the cpp_print() function. That is expected as the <print> library is probably not implemented by your compiler just yet. If you didn’t get any warnings, that is great news as it means the library is already implemented in your compiler. It appears that MSVC is the only major compiler to have implemented the <print> library at this time. Other compilers will add it in the future.

Run the program one last time and you should see the output for all of the demonstration example functions. Since I do not yet have access to the <print> library on my Mac, I get a notification that the library is not yet available.

C standard formatted printing with printf():
Hello, World!
INT_MAX = 2147483647
     Pi =    3.14159

C++ standard printing with std::cout and std::endl and formatting with std::fixed, std::setprecision(), and std::setw():
Hello, World!
INT_MAX = 2147483647
     Pi =    3.14159

C++ standard printing with std::cout and std::endl and <format> based formatting with std::format():
Hello, World!
INT_MAX = 2147483647
     Pi =    3.14159

C++ formatted printing with <print> based std::print() and std::println() is not yet available; expected in C++23.

For the typical programmer, the usage of the std::print() and std::println() functions are extremely similar in size and syntax to the classic C based printf() function and will be a welcomed addition to the language. This should make many folks happy and hopefully end the printf() usage in C++ programs argument.

Additional Resources

  • {fmt} Third Party Open Source Library

Summary

In this tutorial, we learned how to use the new text formatting and printing capabilities of the new C++20 and C++23 specifications.

Specifically, we learned how to print formatted text with strings, integers, and floating point numbers using

  • the printf() function in standard C,
  • the std::cout and std::endl stream objects for printing and the std::fixed stream object along with the std::setprecision() and std::setw() stream functions for formatting in standard C++,
  • the std::cout and std::endl stream objects for printing and the std::format() function for formatting in C++20, and
  • the std::print() and std::println() functions for both printing and formatting in C++23.

Hopefully, this tutorial provided you with a good history of how formatted printing is currently performed in standard C and C++ along with the capabilities we have in the future with the new C++20 and C++23 specifications. Happy programming!

The final program used for this tutorial is available on GitHub. The GitHub version of the code is fully commented to include additional information, such as the program’s description, library references, function parameters, code clarifications, and other details. The comments are also Doxygen compatible in case you want to generate the code documentation yourself. There is also a GCC based Makefile included that you can use to compile and run your program along with generating the documentation if you so choose.

As a reminder, this tutorial was provided as a free service to our valued readers. Please help us continue this endeavor by considering a GitHub sponsorship or a PayPal donation.

Thank you for joining me on this journey and I hope you enjoyed the experience. Please feel free to share your thoughts or questions in the comments section below.

About the author

John Woolsey

John is an electrical engineer who loves science, math, and technology and teaching it to others even more.
 
He knew he wanted to work with electronics from an early age, building his first robot when he was in 8th grade. His first computer was a Timex/Sinclair 2068 followed by the Tandy 1000 TL (aka really old stuff).
 
He put himself through college (The University of Texas at Austin) by working at Motorola where he worked for many years afterward in the Semiconductor Products Sector in Research and Development.
 
John started developing mobile app software in 2010 for himself and for other companies. He has also taught programming to kids for summer school and enjoyed years of judging kids science projects at the Austin Energy Regional Science Festival.
 
Electronics, software, and teaching all culminate in his new venture to learn, make, and teach others via the Woolsey Workshop website.

3 Comments

  • Hi

    For functions
    void c_standard
    void cpp_standard

    why aren’t the std::string arguments passed by const reference and not by value?

    Thanks

    • That is a great question.

      Since the strings a small, I chose the simplest method of passing in the strings. However, in retrospect, passing in the strings by constant reference probably would have been the preferred choice.

    • Since we are talking about modern C++ standards, I decided to take it a step further and update the tutorial to utilize the string_view class introduced in C++17. Hopefully, the code is now more in line with best practices.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.