How to use std::format - C++20
Introduced in c++20, <format>
in a text formatting library based on
three simple principles:
- Placeholder-based formatting syntax, with support for indexed arguments and format specifications.
- Type-safe formatting, using variadic templates for multiple argument support.
- Support for user defined types through custom formatters.
Contents
Usage
const std::string message = std::format("{}, {}!", "hello", "world");
// output: Hello, world!
You can play with placeholders indexes allowing us to chargeorder of even repeat arguments:
std::format("{1}, {0}!", "world", "hello");
std::format("he{0}{0}o, {1}!", "l", "world");
Formatters not only return a string representation of a value, but also allow to customize the output through formattig specifiers. These specifiers are specific to each type formatter, for example the floating point formatters implements precision config:
// Save pi as a string with three decimals of precision:
const std::string pi = std::format("{:.3f}", 3.141592654);
You can also use type options to control how values are displayed:
const std::string id = std::format("{:#x}", 0xa); // "0xa"
Custom types
To integrate you own type and make it work with <format>
, there are two ways:
- Overload the
operator<<
forstd::stream
(as usual with the stream API). - Write a custom formatter for your type.
Overloading operator<< for std::stream
#include <ostream>
#include <format>
enum class State {
On,
Off
};
std::ostream& operator<<(std::ostream& os, const State state) {
switch(state) {
case State::On:
return os << "On";
case State::Off:
return os << "Off";
}
// unreachable
return os;
}
//...
const std::string current_mode = std::format("current mode is {}", Mode::On);
Note this adds the disadvantage that you add the performance overhead of
streams inte the formatting, but it's the easiest way to migrate your existing
types to <format>
if you already integrated them with ostream
.
Writing a custom formatter
Writing a formatter involves specializing the
std::formatter
template
for your type:
template<>
struct std::formatter<State> {
std::format_parse_context::iterator parse(std::format_parse_context& context) {
// ...
}
std::format_parse_context::iterator format(
const State state,
std::format_context& context) {
// ...
}
};
The specification must contain two member functions:
- parse(context)
: In charge of parsing the format specifications in the argument placeholder (If there’s any). That is, it is the function that parses what’s inside the “{}” placeholders of the format strings. If any specifier is found, it must be stored in the std::formatter
object (this
in the context of the function).
format(value, context)
: Formats the given value into the given output formatting context, applying any formatting specification found previously byparse()
. For formatting to a given context you can simply callstd::format_to()
with the iterator provided by the context.
We will not cover parsing in depth here (that are good reference examples) because most of the time you’re better off inheriting from an existing formatter that does the complicated stuff for you:
template<>
struct std::formatter<State> : std::formatter<std::string_view> {
template<typename Context>
auto format(const State state, Context& context) {
switch(state) {
case State::On:
return formatter<std::string_view>::format("On", context);
case State::Off:
return formatter<std::string_view>::format("Off", context);
}
// unreachable
return context.out();
}
}