Console output in C++ is the basic way to display information to the user from a program, whether to show results, debug, or simply verify that something is working.
When starting out, it’s normal to use std::cout, but several ways of printing text coexist in C++: printf, std::cout, std::format, and std::print. Each has its place, its advantages, and its little pitfalls.
In this article, we’ll look at the old familiar printf, the classic std::cout stream, and the modern alternatives std::format and std::print.
Quick Comparison
| Feature | printf | std::cout | std::format | std::print |
|---|---|---|---|---|
| Standard | C / C++ | C++98 | C++20 | C++23 |
| Type safety | Low | High | High | High |
| Syntax | Compact | Verbose | Modern | Modern |
| Prints directly | Yes | Yes | No | Yes |
| Typical use | C, legacy, embedded | Classic C++ | Formatting strings | Modern console |
The Classic: printf
printf (print formatted) comes from the C language and is available in C++ through the <cstdio> header. Despite being quite old, it remains very common in legacy code, embedded systems, and quick examples.
It works based on a format string that contains text and placeholders starting with %. Then we pass the values that fill those placeholders.
#include <cstdio>
int main() {
int age = 35;
double height = 1.75;
printf("I am %d years old and %.2f meters tall\n", age, height);
}Notice it’s quite compact. The major drawback is that printf does not check types with the same safety as modern C++ tools.
Common Specifiers
To use printf, we need to know the format specifiers. If we make a mistake, the program can print garbage or exhibit undefined behavior (and that’s never a fun afternoon).
| Specifier | Common Type | Example |
|---|---|---|
%d or %i | Signed integer (int) | 10, -5 |
%u | Unsigned integer (unsigned int) | 20 |
%f | Floating point (float, double) | 3.14159 |
%c | Character (char) | 'A' |
%s | C-style string (const char*) | "Hello" |
%x / %X | Hexadecimal | fa, FA |
%p | Pointer | memory address |
Beware of std::string
printf does not understand C++ objects like std::string. It expects C strings, that is, const char* pointers.
#include <cstdio>
#include <string>
int main() {
std::string name = "Luis";
// Incorrect: printf expects const char*, not std::string
// printf("Hello %s\n", name);
// Correct
printf("Hello %s\n", name.c_str());
}If you use printf with C++ types, explicitly convert them to types that C can understand. With std::string, that means using .c_str().
std::cout
std::cout lives in <iostream> and is the classic C++ way to write to the console. It uses the << operator, just like we saw in the early examples of the course.
#include <iostream>
#include <string>
int main() {
std::string name = "Luis";
int age = 35;
std::cout << "Hello " << name << ", you are " << age << " years old.\n";
}The advantage of std::cout is that it works well with C++ types and avoids many typical printf errors. The disadvantage is that when there is a lot of formatting, it can become quite verbose.
std::format
C++20 introduced std::format, which allows creating formatted strings using {} as placeholders. It closely resembles the style of Python or other modern formatting libraries.
#include <format>
#include <iostream>
#include <string>
int main() {
std::string name = "Luis";
int age = 35;
std::string text = std::format("Hello {}, you are {} years old.", name, age);
std::cout << text << "\n";
}std::format does not print directly. It returns a std::string, which you can then store, send to a log, write to a file, or display on the console.
std::print and std::println
C++23 adds std::print and std::println, which are the direct version for printing using the same formatting system.
#include <print>
#include <string>
int main() {
std::string name = "Luis";
int age = 35;
std::println("Hello {}, you are {} years old.", name, age);
}The difference is simple:
std::printdoes not add a newline.std::printlnadds a newline at the end.
std::print belongs to C++23, so its availability depends on the compiler and the standard library you use. If your environment doesn’t support it yet, std::format + std::cout is a reasonable alternative.
Advanced Formatting
Inside the braces, we can add format specifiers. For example, for decimals or hexadecimal:
double pi = 3.14159265;
int color = 255;
std::println("Pi is {:.2f} and the color is {:X}", pi, color);In this example, {:.2f} shows two decimal places and {:X} shows the integer in hexadecimal with uppercase letters.