
std::format
added in C++20 is a strong helper for numerous textual content formatting duties. On this weblog publish, we’ll have enjoyable and print some tables with it. You’ll see the “outdated” C++17 model and evaluate it towards the C++20 type.
As an train to find out about std::format
, we will attempt printing some extra superior buildings than simply “Howdy World”.
For instance, if we’ve a map of knowledge:
constexpr size_t Rows = 5;
const std::map<std::string, std::array<double, Rows>> productToOrders{
{ "apples", {100, 200, 50.5, 30, 10}},
{ "bananas", {80, 10, 100, 120, 70}},
{ "carrots", {130, 75, 25, 64.5, 128}},
{ "tomatoes", {70, 100, 170, 80, 90}}
};
We wish to print them within the following desk:
apples bananas carrots tomatoes
100.00 80.00 130.00 70.00
200.00 10.00 75.00 100.00
50.50 100.00 25.00 170.00
30.00 120.00 64.50 80.00
10.00 70.00 128.00 90.00
C++17 model
Let’s attempt the next code from C++17:
// print headers:
for (const auto& [key, val] : productsToOrders)
std::cout << std::setw(10) << key;
std::cout << 'n';
// print values:
for (size_t i = 0; i < NumRows; ++i) {
for (const auto& [key, val] : productsToOrders) {
std::cout << std::setw(10) << std::mounted
<< std::setprecision(2) << val[i];
}
std::cout << 'n';
}
Structured bindings from C++17 assist right here, however as you may see, typically we use solely keys
and typically solely values
.
Enhancements into C++20
Okay, we’ve fundamental code, however let’s attempt enhancing it.
Max column width
The primary small factor is that we use a hard and fast size of fields, so if some textual content in a column is bigger than ten characters, we’ll have some overflow.
We will write the next helper perform:
template <typename T>
size_t MaxKeyLength(const std::map<std::string, T>& m) {
size_t maxLen = 0;
for (const auto& [key, val] : m)
if (key.size() > maxLen)
maxLen = key.size();
return maxLen;
}
And use it:
const auto ColLength = MaxKeyLength(productsToOrders) + 2;
Later we will move it to all std::setw()
capabilities.
The code is ok, because it handles all maps which have std::string
as the important thing, and values could be of a special sort. Nonetheless, it’s higher to depend on the extra generic customary algorithm.
The primary strategy could be the next:
template <typename T>
size_t MaxKeyLength(const std::map<std::string, T>& m) {
auto res = std::ranges::max_element(m,
[](const auto& a, const auto& b) {
return a.first.size() < b.first.size();
});
return res->first.size();
}
However we will additionally use a helpful view that returns solely keys
:
template <typename T>
size_t MaxKeyLength(const std::map<std::string, T>& m) {
auto res = std::ranges::max_element(std::views::keys(m),
[](const auto& a, const auto& b) {
return a.size() < b.size();
});
return (*res).size();
}
Including std::format
Let’s now exchange output with std::cout
into std::format
calls:
// headers:
for (const auto& identify : std::views::keys(productsToOrders))
std::cout << std::format("{:*>{}}", identify, ColLength);
std::cout << 'n';
The code makes use of this particular format specifier: "{:>{}}"
:
{:fill-and-align signal width precision sort}
The code makes use of *
because the placeholder character and >
as an alignment (we will additionally use <
for left or ^
for heart). After which, for the width, we use {}
, which factors to a size that comes simply after identify
.
Equally, for values, we will implement the next loop:
// print values:
for (size_t i = 0; i < NumRows; ++i) {
for (const auto& values : std::views::values(productsToOrders)) {
std::cout << std::format("{:>{}.2f}", values[i], ColLength);
}
std::cout << 'n';
}
After we run this code, we must always see the next:
****apples***bananas***carrots**tomatoes
100.00 80.00 130.00 70.00
200.00 10.00 75.00 100.00
50.50 100.00 25.00 170.00
30.00 120.00 64.50 80.00
10.00 70.00 128.00 90.00
You possibly can play with this model @Compiler Explorer
Facet observe: as of Feb 2023, it seems to be like all main compilers (MSVC, Clang 17, GCC 13) help
std::format
and evenstd::chrono
calendar!
Including dates from std::chrono
Whereas our desk seems to be tremendous, it lacks some particulars. What do these numbers in rows imply?
We will add the next:
std::chrono::year_month_day startDate{2023y, month{February}, 20d};
After which add one column:
std::cout << std::format("{:>{}}", "date", ColLength);
After which, print dates earlier than printing the worth set:
const auto nextDay = sys_days{ startDate } + days{ i };
std::cout << std::format("{:>{}}", nextDay, ColLength);
Now we’ve the next desk, which might be simpler to learn:
Orders:
date apples bananas carrots tomatoes
2023-02-20 100.00 80.00 130.00 70.00
2023-02-21 200.00 10.00 75.00 100.00
2023-02-22 50.50 100.00 25.00 170.00
2023-02-23 30.00 120.00 64.50 80.00
2023-02-24 10.00 70.00 128.00 90.00
As you may see, the code makes use of a beginning date, then provides someday and prints it. Surprisingly we’ve to transform the date into sys_days
earlier than including a brand new day. The preliminary date format year_month_date
doesn’t have days precision.
Play with code @Compiler Explorer
This text began as a preview for Patrons, typically even months earlier than the publication.
If you wish to get additional content material, previews, free ebooks and entry to our Discord server, be a part of the C++ Tales Premium membership.
Abstract
As we speak’s experiment went from a easy desk printing code utilizing C++17 right into a fancier model from C++20. The improved code was “nicer” and simpler to write down and keep. Moreover, we acquired working date-time dealing with capabilities in simply a few traces of code! Which wasn’t doable till C++20.
Again to you
Have you ever tried std::format
or perhaps {fmt}
?
Share your feedback beneath.