Enjoyable with printing tables with std::format and C++20


Programmer's Academy

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 even std::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.

(Visited 7 times, 1 visits today)

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
Ask ChatGPT
Set ChatGPT API key
Find your Secret API key in your ChatGPT User settings and paste it here to connect ChatGPT with your Tutor LMS website.
0
Would love your thoughts, please comment.x
()
x