How to use std::format - C++20

26 January 2023

Introduced in c++20, <format> in a text formatting library based on three simple principles:

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:

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).

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();
  }
}

Links

Sources