std::format of user-defined types?
Asked Answered
L

2

27

In C++20 - how do you make a user-defined type compatible with std::format?

For example, let's say I have a type called Point:

struct Point {
    int x;
    int y;
};

with its operator<< defined:

inline std::ostream&
operator<<(std::ostream& o, Point pt)
{ return o << "[" << pt.x << << ", " << pt.y << "]"; }

then will the following program output Hello [3, 4]!?

int main() {
   Point pt{3,4};
   std::cout << std::format("Hello {}!\n", pt);
}

If yes - why and how?

If no - what do I have to add to the definition of Point to to make it work?

Lucan answered 25/1, 2020 at 12:14 Comment(1)
You should overload std::formatter<Ti, CharT>Carbon
A
36

std::format doesn't support operator<<, you need to provide a formatter specialization for your type (Point) instead. The easiest way to do it is by reusing one of existing formatters, e.g. std::formatter<std::string>:

template <>
struct std::formatter<Point> : std::formatter<std::string> {
  auto format(Point p, format_context& ctx) const {
    return formatter<string>::format(
      std::format("[{}, {}]", p.x, p.y), ctx);
  }
};

This will give you all format specifications supported by std::string out of the box. Here is an example of formatting Point with center alignment padded with '~' to 10 characters:

auto s = std::format("{:~^10}", Point{1, 2});
// s == "~~[1, 2]~~"

which is nontrivial to achieve with iostreams.

Aliber answered 26/1, 2020 at 1:41 Comment(10)
If there is no std::formatter for a given type then why didn't we define that std::format falls back to the operator<< if one is available?Lucan
Fall back on operator<< introduces a lot of problems, for example, output depending on visibility of formatter specialization and even potentially ODR violations. This is why it was not included in std::format.Aliber
Note that en.cppreference.com/w/cpp/utility/format currently claims "... and reuse some of [I/O streams] infrastructure such as overloaded insertion operators for user-defined types." which is inconsistent with this answer.Ciscaucasia
cppreference is wrong in this case, std::format has its own extension mechanism and doesn't use operator<<.Aliber
Note that the incorrect statement regarding format and operator<< has been removed from cppreference.Claribelclarice
Which compilers support std::format? I tried this solution in Compiler Explorer here with no luck.Cretan
std::format is purely library, not compiler, feature and it's supported in the most recent version of libc++ and partially in libstdc++. I am not sure if either is available on CE yet.Aliber
I assume "x86-64 gcc (trunk)" on CE will have been built in the last few days, including libstdc++. I wonder if a trunk build of libc++ is available online.Cretan
Using std::format_context& as the type for the ctx parameter to format is not cross-platform, because the C++ standard does not guarantee any specific type for that parameter (specifically, it fails in some versions of clang). Instead, it is necessary to template on the type of ctx (i.e. by changing format_context& ctx to auto& ctx). See here.Chuvash
@ChristopherMiller That information is out of date. The standard requires type erasure via a single format_context type.Aliber
T
9

You have to specialize std::formatter for your type.

namespace std
{
    template<class CharT>
    struct formatter<Point, CharT>
    {  
        template <typename FormatParseContext>
        auto parse(FormatParseContext& pc)
        {
            // parse formatter args like padding, precision if you support it
            return pc.end(); // returns the iterator to the last parsed character in the format string, in this case we just swallow everything
        }

        template<typename FormatContext>
        auto format(Point p, FormatContext& fc) 
        {
            return std::format_to(fc.out(), "[{}, {}]", p.x, p.y);
        }
    };
}

I don't think the ostream operator will work but I have no sources to support this claim.

Tristram answered 25/1, 2020 at 12:56 Comment(4)
Isn't partial specialization of standard types undefined behavior?Arched
@StaceyGirl I don't think so. Quote from cppreference: "It is allowed to add template specializations for any standard library [...] template to the namespace std only if the declaration depends on at least one program-defined type [...]"Tristram
You are right, ostream operator<< is not supported.Aliber
@Aliber Oh mr. format himself to the rescue. Thanks for the edit. Wasn't sure from what was written on cppreference.Tristram

© 2022 - 2024 — McMap. All rights reserved.