Search code examples
c++formatc++20stdformat

How to forward formatting information from std::format


I am trying to use c++20's std::format on msvc.
When I try to define a format for a custom type Person, I hope to be able to use the external std::format("{:x}{:*^10}", person) in "{:x}{:*^10}" is forwarded to std::formatter<Person>::fomat and used directly in std::formatter<Person>::fomat
for example:

#include <format>
#include <iostream>
#include <string>
#include <string_view>

class Person {
public:
    int age;
    std::string name;

};

template <>
struct std::formatter<Person> : std::formatter<std::string> {
    template <typename FormatContext>
    auto format(const Person& p, FormatContext& ctx) {
        // I hope that the "forwarded format" here is "{:x}{:*^10}" in the format information of ::std::format in main. \
          This code is of course not runnable. I want to know How can I use an external format in formatter<Person>::foaramt like this
        return format_to(ctx.out(),"forwarded format",p.age,p.name);
    }
};

int main() {
    Person person{ 30,"John Doe" };
    std::cout << std::format("{:x}{:*^10}", p) << '\n';
    return 0;
}

I hope to be able to use the forwarded formatting information "{:x}{:*^10}" directly in std::formatter::format, Instead of changing std::foramt("{:x}{:*^10}",person) to std::format("{:x}{:*^10}",person.age,person.name)


Solution

  • You can define a string_view variable in std::formatter<Person> to store formatting information.

    Then, calculate the format string for members in the parse() function and assign it to this variable, and use it in the format() function.

    (For simplicity, some compile-time checks inside the parse function are omitted)

    template <>
    struct std::formatter<Person> {
      std::string_view fmt_str; // formatting information
    
      template<typename ParseContext>
      constexpr auto parse(ParseContext& ctx) {
        auto begin = ctx.begin();                       // ctx is "x}{:*^10}"
        auto it = std::formatter<int>{}.parse(ctx);     // now, it point to "}{:*^10}"
        it = std::ranges::find(it + 1, ctx.end(), ':'); // now, it point to ":*^10}"
        ctx.advance_to(it + 1);                         // now, ctx is "*^10}"
        it = std::formatter<std::string>{}.parse(ctx);  // now, it point to "}"
        fmt_str = {begin - 2, it + 1};                  // now fmt_str is "{:x}{:*^10}"
        return it;
      }
    
      template <typename FormatContext>
      auto format(const Person& p, FormatContext& ctx) const {
        return std::vformat_to(ctx.out(), fmt_str, std::make_format_args(p.age, p.name));
      }
    };
    

    Demo