Formatting User-Defined Types in C++20
This post is a cross-post from www.ModernesCpp.com.
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:
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.
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 }.
Let me apply the theory and start with the first example.
?
Modernes C++ Mentoring
Be part of my mentoring programs:
Do you want to stay informed: Subscribe.
?
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.out 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. I would like to improve that.
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, Kris Kafka, 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, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, 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, moon, and Philipp Lenk.
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.
Online Seminars (German)
Contact Me
Modern C++ programming specialist
9 个月When is the input method going to evolve? After `std::print`, we're gonna need a `std::scan` as well. Is there any vision on moving toward s deprecation of `<iostream>`?