FMT C++ library: allow user to set format specifiers for custom type
Asked Answered
M

2

13

I have a custom type, for example

struct custom_type
{
    double value;
};

I want to set a custom FMT formatter for this type. I do the following and it works:

namespace fmt
{
    template <>
    struct formatter<custom_type> {
        template <typename ParseContext>
        constexpr auto parse(ParseContext &ctx) {
        return ctx.begin();
    };

    template <typename FormatContext>
    auto format(const custom_type &v, FormatContext &ctx) {
        return format_to(ctx.begin(), "{}", v.value);
    }
};

But the problem is, that output format is set by template code, with this "{}" expression. And I want to give a user opportunity to define the format string himself.

For example:

custom_type v = 10.0;
std::cout << fmt::format("{}", v) << std::endl;    // 10
std::cout << fmt::format("{:+f}", v) << std::endl; // 10.000000

How can I do this?

Currently, when I set the custom format string, I get

 what():  unknown format specifier
Microampere answered 20/5, 2019 at 11:40 Comment(1)
Your formatter type is missing a } after the function. Also, in format_to(), I believe it should be ctx.out() like all other examples I can find. It doesn't compile for me as it is.Wring
E
9

The easiest solution is to inherit formatter<custom_type> from formatter<double>:

template <> struct fmt::formatter<custom_type> : formatter<double> {
  auto format(custom_type c, format_context& ctx) {
    return formatter<double>::format(c.value, ctx);
  }
};

https://godbolt.org/z/6AHCOJ

Etom answered 25/10, 2019 at 4:3 Comment(0)
M
1

Finally I have done it. I will save it here, in case someone else needs it.

    template <>
    struct formatter<custom_type> {
        template <typename ParseContext>
        constexpr auto parse(ParseContext &ctx) {
            auto it = internal::null_terminating_iterator<char>(ctx);
            std::string tmp;
            while(*it && *it != '}') 
            {
                tmp += *it;
                ++it;
            }
            m_format=tmp;
            return internal::pointer_from(it);
        }

        template <typename FormatContext>
        auto format(const custom_type &v, FormatContext &ctx) {
            std::string final_string;
            if(m_format.size()>0)
            {   
                final_string="{:"+m_format+"}";
            }
            else
            {
                final_string="{}";
            }
            return format_to(ctx.begin(), final_string, v.value);
        }
        mutable std::string m_format;
    };
Microampere answered 20/5, 2019 at 15:2 Comment(1)
You don't really need to use any internal APIs or build a format string in format. Just reuse a specialization of formatter that you need.Etom

© 2022 - 2024 — McMap. All rights reserved.