C++20: Extend std::format for User-Defined Types

Contents[Show]

Peter Gottschling presented in his last post "std::format in C++20" the basics to the new formatting library in C++20. In today's post Peter writes about the formatting of user-defined types.

 TimelineCpp20CoreLanguage

Our first example of template specialization is the customization of the new format library introduced in order to support user types.

Formatting User-Defined Types

As example, we choose the dmc::vector (dmc is the namespace from the book "Discovering Modern C++" by the author) class for which we like to specify the formatting of the single values. In addition, we want to replace the enclosing brackets by curly braces when the format string contains the letter 'c'. To this end, we have to specialize the class std::formatter (or fmt::formatter for the prototype library fmt). Our specialization shall contain the methods parse and format.

Let's start with the former:

template <typename Value>
struct formatter<dmc::vector<Value>>
{
    constexpr auto parse(format_parse_context& ctx)
    {
        value_format= "{:";        
        for (auto it= begin(ctx); it != end(ctx); ++it) {
            char c= *it;
            if (c == 'c')
                curly= true;
            else
                value_format+= c;
            if (c == '}')
                return it;
        }
        return end(ctx);
    }
    // ...
    bool        curly{false};
    std::string value_format;
};

 

As an argument, the parse context is given whose begin iterator points to the first character of the format specification, i.e.~the first character after the colon and in its absence the first character after the opening brace. We copy the format specification almost identically to our local value_format, only our special character 'c' is skipped. For simplicity, we assume that the format doesn't contain any opening or closing brace so that the next closing brace terminates our format string. Finally, we return the iterator pointing to the closing brace or the end iterator.

With this information we can output our vector in the method format:

template <typename Value>
struct formatter<dmc::vector<Value>>
{
    template <typename FormatContext>
    auto format(const dmc::vector<Value>& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        format_to(out, curly ? "{{" : "[");
        if (v.size() > 0)
            format_to(out, value_format, v[0]);
        for (int i= 1; i < v.size(); ++i)
            format_to(out, ", " + value_format, v[i]);
        return format_to(out, curly ? "}}" : "]");
    }
    // ...
};

 

First, we take a reference to the output buffer. Then we write the opening brace or bracket to it. Since braces have a special meaning in the format library, we need an escape sequence of double braces. The remaining output is equivalent to the ostream output. Finally, we return the output buffer.

Now we can try various formats:

dmc::vector<double> v{1.394, 1e9, 1.0/3.0, 1e-20};
print("v with empty format = {:}.\n", v);
print("v with f = {:f}.\n", v);
print("v curly with f = {:fc}.\n", v);
print("v width 9, 4 digits = {:9.4f}.\n", v);
print("v scient. = {:ec}.\n", v);

 

and see the according outputs:

v with empty format = [1.394, 1000000000.0, 0.3333333333333333, 1e-20].
v with f = [1.394000, 1000000000.000000, 0.333333, 0.000000].
v curly with f = {1.394000, 1000000000.000000, 0.333333, 0.000000}.
v width 9, 4 digits = [   1.3940, 1000000000.0000,    0.3333,    0.0000].
v scient. = {1.394000e+00, 1.000000e+09, 3.333333e-01, 1.000000e-20}.

Altogether, since the new formatting is:

  • Compact: demonstrated in the examples above
  • Adaptable: to various output orders
  • Type-safe: an exception is thrown when an argument doesn't match
  • Extensible: can be extended to user-defined types

For those reasons, it is superior to the preceding techniques, and we therefor strongly advise to use it as soon as sufficient compiler support is available.

Thanks once more to Peter Gottschling for providing a compact introduction to std::format. Let me add a few words to complete his introduction to the formatting library.

Try It Out

As Peter already mentioned it, the GitHub hosted fmt library is a prototype for the new formatting library in C++20. The front page of the fmt project includes a few straightforward examples and performance numbers. These examples include a direct link to the compiler explorer for executing the example.

Thanks to the new formatting library, you can display time durations of the chrono library:

#include <fmt/chrono.h>

int main() {
  using namespace std::literals::chrono_literals;
  fmt::print("Default format: {} {}\n", 42s, 100ms);
  fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
}

 

Executing the program on the compiler explorer gives you the following output:

formatChrono

Porting to C++20

Porting the previous program from fmt to the C++20 format library is a piece of cake. You have to use the C++ standard header chrono and iostream. Additionally, replace the call fmt::print with the function std::format and push the result to std::cout. std::format returns a string according to the given format string and an optional local.

 

// formatChrono.cpp

#include <chrono>
#include <iostream>

int main() {
  using namespace std::literals::chrono_literals;
  std::cout << std::format("Default format: {} {}\n", 42s, 100ms) << "\n";
  std::cout << std::format("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s) << "\n";
}

 

What's next?

In my next post, I continue with the convenience functions. With C++20, you can calculate the midpoint of two values, check if a std::string start or ends with a substring, and create callables with std::bind_front.

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Wolfgang Gärtner,  Louis St-Amour, Stephan Roslen, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Avi Kohn, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, Kai, and Sudhakar Balagurusamy.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, and Dendi Suhubdy

 

 

Seminars

I'm happy to give online-seminars or face-to-face seminars world-wide. Please call me if you have any questions.

Standard Seminars 

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.





Contact Me



    • Tel.: +49 7472 917441

    • Mobil: +49 152 31965939



Modernes C++,

RainerGrimmSmall

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 2863

Yesterday 5163

Week 48020

Month 202615

All 4823509

Currently are 126 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments