24

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?

Acorn
  • 24,970
  • 5
  • 40
  • 69
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319

2 Answers2

26

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.

vitaut
  • 49,672
  • 25
  • 199
  • 336
  • 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? – Andrew Tomazos Jan 29 '20 at 05:07
  • 3
    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`. – vitaut Jan 29 '20 at 14:45
  • Note that https://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. – jwnimmer-tri Aug 21 '22 at 15:52
  • cppreference is wrong in this case, std::format has its own extension mechanism and doesn't use operator<<. – vitaut Aug 21 '22 at 23:54
  • 1
    Note that the incorrect statement regarding `format` and `operator<<` has been removed from cppreference. – Max Truxa Jan 06 '23 at 11:12
  • Which compilers support `std::format`? I tried this solution in Compiler Explorer [here](https://godbolt.org/z/18vdqoGdb) with no luck. – user2023370 Mar 29 '23 at 18:02
  • 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. – vitaut Mar 29 '23 at 19:30
  • 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. – user2023370 Mar 31 '23 at 09:25
8

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.

vitaut
  • 49,672
  • 25
  • 199
  • 336
Timo
  • 9,269
  • 2
  • 28
  • 58
  • Isn't _partial_ specialization of standard types undefined behavior? –  Jan 25 '20 at 14:11
  • 4
    @StaceyGirl I don't think so. Quote from [cppreference](https://en.cppreference.com/w/cpp/language/extending_std): _"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 [...]"_ – Timo Jan 25 '20 at 14:20
  • You are right, ostream operator<< is not supported. – vitaut Jan 26 '20 at 01:49
  • 1
    @vitaut Oh mr. format himself to the rescue. Thanks for the edit. Wasn't sure from what was written on cppreference. – Timo Jan 26 '20 at 08:47