Specializing std::formatter not working?

It seems like this code compiles with libstdc++, but not libc++. I got it (with minor changes) from the book C++20 The Complete Guide, by Josuttis. (godbolt code link)

Is this supposed to work?

struct Foo { 
    int value = 123;
}; 

#include <format> 
#include <iostream> 

template<> 
struct std::formatter<Foo> 
{ 
  constexpr auto parse(std::format_parse_context& ctx) { 
    return ctx.begin(); 
  } 
  auto format(const Foo& obj, std::format_context& ctx) const { 
    return std::format_to(ctx.out(), "{}", obj.value); 
  } 
}; 

int main() {
    std::cout << std::format("{}\n", Foo());
}

At first sight this looks like a libc++ bug. I don’t have time to look at it today, but I’ll try to look at it tomorrow.

I’ve verified it and libc++ is indeed wrong, however I’m not convinced whether this is worth fixing:

  • C++20 has a textual requirement for what a proper std::formatter specialization is.
  • C++23 ranges has a formattable concept.

Your formatter satisfies the C++20 requirements, but not the C++23 concept as implemented by libstdc++ and libc++. (Compiler Explorer) So when you want to format a std::vector<Foo> it correctly fails on libc++. (On libstdc++ it fails, because they don’t have a formatter for std::vector yet.)

The easiest fix for you would be to use auto format(const Foo& obj, auto& ctx) const as format member function.

I will give this some more thoughts, to see what the best solution is. (There is a bit of an issue with the concept, so your formatter might be formattable on MSVC STL if they make different design choices. In fact it’s possible to create a formatter that’s formattable for libc++ but not for libstdc++.)

1 Like

Thank you. I’m not far enough along teaching myself C++ to understand why it’s failing and have an opinion on this, but your quick fix lets me get started making formatters for my own simple types.

It’s failing since formatters are expected to work with different format_context’s. So creating a formatter only for std::format_context is not very generic. The context depends the type of output iterator used.

Have fun learning C++!

I was having the same problem. Looked at how libc++ was implemented internally and found they use
auto instead of std::format_context in the format function. Make that change and the specialization works.

template<> 
struct std::formatter<Foo> 
{ 
  constexpr auto parse(std::format_parse_context& ctx) { 
    return ctx.begin(); 
  } 

  auto format(const Foo& obj, auto& ctx) const { // Change 'std::format_context' to 'auto'
    return std::format_to(ctx.out(), "{}", obj.value); 
  } 
}; 

Thanks, this is indeed the way to fix it. Did you also read Josuttis’ book?

Not C++20, but I am the proud owner of another one he co-authored - C++ Templates.