Formatting User-Defined Types in C++20
Additionally, to the basic types and std::string
, you can also format user-defined types in C++20.
So far, I have formatted basic types and std::string.
Here are my previous posts:
- The Formatting Library in C++20
- The Formatting Library in C++20: The Format String
- The Formatting Library in C++20: The Format String (2)
std::formatter
enables it to format user-defined types. You have to specialize the class std::formatter for the user-defined type. In particular, you must implement the member functions parse
, and format
.
parse
: This function parses the format string and throws astd::format_error
in case of an error.
The function parse
should be constexpr
to enable compile-time parsing. It accepts a parse context (std::format_parse_context
) and should return the last character for the format specifier (the closing }). When you don’t use a format specifier, this is also the first character of the format specifier.
The following lines show a few examples of the first character of the format specifier:
"{}" // context.begin() points to } "{:}" // context.begin() points to } "{0:d}" // context.begin() points to d} "{:5.3f}" // context.begin() points to: 5.3f} "{:x}" // context.begin() points to x} "{0} {0}" // context.begin() points to: "} {0}"
context.begin()
points to the first character of the format specifier and context.end()
to the last character of the entire format string. When you provide a format specifier, you have to parse all between context.begin()
and context.end()
and return the position of the closing }
.
format
: This function should beconst
. It gets the valueval
, and the format contextcontext. format
formats the valueval
, and writes it according to the parsed format tocontext.out().
Thecontext.out()
return value can be directly fed intostd::format_to. std::format_to
has to return the new position for further output. It returns an iterator that represents the end of the output.
Let me apply the theory and start with the first example.
A Formatter for a Single Value
// formatSingleValue.cpp #include <format> #include <iostream> class SingleValue { // (1) public: SingleValue() = default; explicit SingleValue(int s): singleValue{s} {} int getValue() const { // (2) return singleValue; } private: int singleValue{}; }; template<> // (3) struct std::formatter<SingleValue> { constexpr auto parse(std::format_parse_context& context) { // (4) return context.begin(); } auto format(const SingleValue& sVal, std::format_context& context) const { // (5) return std::format_to(context.out(), "{}", sVal.getValue()); } }; int main() { std::cout << '\n'; SingleValue sVal0; SingleValue sVal2020{2020}; SingleValue sVal2023{2023}; std::cout << std::format("Single Value: {} {} {}\n", sVal0, sVal2020, sVal2023); std::cout << std::format("Single Value: {1} {1} {1}\n", sVal0, sVal2020, sVal2023); std::cout << std::format("Single Value: {2} {1} {0}\n", sVal0, sVal2020, sVal2023); std::cout << '\n'; }
SingleValue
(line 1) is a class having only one value. The member function getValue
(line 2) returns this value. I specialize std::formatter
(line 3) on SingleValue
. This specialization has the member functions parse
(line 4) and format
(line 5). parse
returns the end of the format specification. The end of the format specification is the closing }. format
formats the value, and context.ou
t creates an object passed to std::format_to. format
returns the new position for further output.
Executing this program gives the expected result:
This formatter has a severe drawback. It does not support a format specifier. Let me improve that.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
A Formatter Supporting a Format Specifier
Implementing a formatter for a user-defined type is pretty straightforward when you base your formatter on a standard formatter. Basing a user-defined formatter on a standard formatter can be done in two ways: delegation and inheritance.
Delegation
The following formatter delegates its job to a standard formatter.
// formatSingleValueDelegation.cpp #include <format> #include <iostream> class SingleValue { public: SingleValue() = default; explicit SingleValue(int s): singleValue{s} {} int getValue() const { return singleValue; } private: int singleValue{}; }; template<> // (1) struct std::formatter<SingleValue> { std::formatter<int> formatter; // (2) constexpr auto parse(std::format_parse_context& context) { return formatter.parse(context); // (3) } auto format(const SingleValue& singleValue, std::format_context& context) const { return formatter.format(singleValue.getValue(), context); // (4) } }; int main() { std::cout << '\n'; SingleValue singleValue0; SingleValue singleValue2020{2020}; SingleValue singleValue2023{2023}; std::cout << std::format("{:*<10}", singleValue0) << '\n'; std::cout << std::format("{:*^10}", singleValue2020) << '\n'; std::cout << std::format("{:*>10}", singleValue2023) << '\n'; std::cout << '\n'; }
std::formatter<SingleValue>
(line 1) has a standard formatter for int: std::formatter<int> formatter
(line 2). I delegate the parsing job to the formatter (line 3). Accordingly, the formatting job format is also delegated to the formatter (line 4).
The program’s output shows that the formatter supports fill characters and alignment.
Inheritance
Thanks to inheritance, implementing the formatter for the user-defined type SingleValue
is a piece of cake.
// formatSingleValueInheritance.cpp #include <format> #include <iostream> class SingleValue { public: SingleValue() = default; explicit SingleValue(int s): singleValue{s} {} int getValue() const { return singleValue; } private: int singleValue{}; }; template<> struct std::formatter<SingleValue> : std::formatter<int> { // (1) auto format(const SingleValue& singleValue, std::format_context& context) const { return std::formatter<int>::format(singleValue.getValue(), context); } }; int main() { std::cout << '\n'; SingleValue singleValue0; SingleValue singleValue2020{2020}; SingleValue singleValue2023{2023}; std::cout << std::format("{:*<10}", singleValue0) << '\n'; std::cout << std::format("{:*^10}", singleValue2020) << '\n'; std::cout << std::format("{:*>10}", singleValue2023) << '\n'; std::cout << '\n'; }
I derive std::formatter<SingleValue>
from std::formatter<int>
(line 1). Only the format
function must be implemented. The output of this program is identical to the output of the previous program formatSingleValueDelegation.cpp
.
Delegating to a standard formatter or inheriting from one is a straightforward way to implement a user-defined formatter. This strategy only works for user-defined types having one value.
What’s Next?
In my next blog, I will implement a formatter for a user-defined type having more than one value.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Seminars
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
- C++20
Contact Me
- Mobil: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,