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

[ad_1]

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.

[ad_2]

Leave a Reply

Your email address will not be published. Required fields are marked *