I’m happy to announce that the Text Formatting proposal (std::format) made it into the C++20 Committee
Draft at the Cologne meeting, just in time before the feature freeze. This
concludes a three-year long design, implementation, and standardization effort.
Here’s a brief history of the proposal aka “what took you so long?”:
2016-08-17:
2016-08-27:
2017-07-10 to 15, Toronto, Canada:
2018-03-12 to 17, Jacksonville, USA:
{} instead of %)format("{0:%Y-%m-%d}", t))format_to_n)2018-06-04 to 09, Rapperswil, Switzerland:
std::format instead of std::fmt::format or
similar)2018-11-05 to 10, San Diego, USA:
std::vformat that doesn’t depend on argument types and gives you
compact per-call binary code)std::formatter<T>)std::to_chars) was approved2019-02-18 to 23, Kona, USA:
2019-07-15 to 20, Cologne, Germany:
Contrary to the usual “design-by-committee” narrative, the standardization process was tremendously beneficial both for the proposal and the {fmt} library, resulting in numerous improvements and here are just a few examples:
Iterator support
The addition of iterator-based std::format_to and std::format_to_n functions
makes formatting to arbitrary output targets such as arrays or containers
as easy and natural as calling std::copy or std::copy_n:
std::vector<char> buf;
std::format_to(std::back_inserter(buf), "{}", 42);
// buf contains "42"
or
char buf[10];
auto result = std::format_to_n(buf, sizeof(buf), "{}", 42);
// buf contains "42" and result.out points to the end of the output.
// No dynamic memory allocations.
Precomputing the output size
As the name suggests, formatted_size gives you the output size which
could be useful for preallocating a buffer:
auto size = std::formatted_size("{}", 42); // size == 2
std::vector<char> buf(size);
std::format_to(buf.data(), "{}", 42);
Compile-time processing of format string
Although not exposed through the top-level API yet, the design and the
extension API fully support constexpr format string parsing which also
applies to user-defined types. This required separation of parser and
formatter contexts that hold parsing and formatting state respectively and
generally resulted in a cleaner API:
struct std::formatter<MyType> {
constexpr auto parse(parse_context& ctx) {
// Parses the format string range [ctx.begin(), ctx.end()).
// Can be omitted if inherited from another formatter.
}
auto format(const MyType& value, format_context& ctx) {
// Formats value and writes into ctx.out().
// Can be delegated to another formatter.
}
};
This will allow compile-time format string checks and even translating your format strings into formatter objects and eliminating any runtime overhead (which is usually pretty small though).
Default representation of floating-point numbers
Building upon C++17’s std::to_chars and following the example
of Python 3’s str.format, std::format provides the shortest
correctly-rounded floating point representation as the default. In particular
this means, that unlike with printf, there is no precision loss:
auto s = std::format("{}", M_PI); // s == "3.141592653589793"
char buf[20];
sprintf(buf, "%g", M_PI); // buf contains "3.14159"
In addition to the P0645 Text Formatting there were several other formatting papers approved at Cologne:
P1361 Integration of chrono with text formatting written in
collaboration with Daniela Engert and Howard E. Hinnant. This paper makes it
possible to format all chrono types with std::format and removes now
redundant std::chrono::format. Among other things, formatting multiple
objects in one go becomes much easier:
void print_birthday(std::string_view name,
const std::chrono::year_month_day& birthday) {
std::cout << std::format("{0}'s birthday is {1:%Y-%m-%d}.\n", name, birthday);
}
and you can avoid dynamic memory allocations:
std::array<char, 100> buf;
std::format_to_n(buf.data(), buf.size(), "{:%Y-%m-%d}", date);
P1652 Printf corner cases in std::format
written by Zhihao Yuan. This paper contains a number of critical bug fixes
for P0645 Text Formatting. For example, it makes formatting of float
round trip (previously float was converted to double as in printf):
std::string s = std::format("{}", 3.31f); // s == "3.31"
Without the fix s would contain “3.309999942779541”.
P1636 Formatters for library types by
Lars Gullik Bjønnes. This paper makes a number of standard library types
such as std::complex and std::filesystem::path formattable with
std::format, e.g.
auto s = std::format("{}", 1 + 2i); // s == (1, 2)
It was approved by the Library Evolution Working Group that deals with the design and API, but there was no wording review yet.
Thanks to Alberto Barbati, Antony Polukhin, Beman Dawes, Bengt Gustafsson, Daniel Krügler, Daniela Engert, Eric Niebler, Jason McKesson, Jeffrey Yasskin, Joël Lamotte, Lars Gullik Bjønnes, Lee Howes, Louis Dionne, Marshall Clow, Matt Clarke, Michael Park, Sean Middleditch, Sergey Ignatchenko, Thiago Macieira, Titus Winters, Tomasz Kamiński, Zach Laine, Zhihao Yuan, participants of the Library Evolution Working Group and the Library Working Group for their feedback, support, constructive criticism and contributions to the proposal. Special thanks to Howard E. Hinnant who encouraged me to write the proposal and gave useful early advice on how to go about it.
Also thanks to my current and past managers at Facebook for supporting my standardization work and the company for paying for my trips to standards committee meetings.
And last but not least thanks to the many contributors to the {fmt} library for their work.
P0645 has built the foundation for modern, efficient, and extensible text formatting. However, due to time constraints it left unanswered a number of questions that will be addressed by future proposals:
Unicode support: we need to provide formatting functions that work with
char8_t and other Unicode unit and corresponding string types,
correctly handling format specifications expressed in more high-level terms
than code units.
Compile-time format strings: as mentioned earlier, the extension API is
constexpr-ready but we need a convenient top-level API to make use of it.
Formatted I/O: it would be nice to have a better integration between formatting and I/O than just passing strings around as in
std::cout << std::format("The answer is {}.", 42);
Q: I want to use this now, not wait until C++20 is available and std::format
is implemented in my standard library. What should I do?
A: All of this and much more has been implemented in the {fmt} library which works on pretty much anything that considers itself a C++ compiler.
comments powered by Disqus