This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4240. The formattable type is not a formattable type

Section: 28.5.6.3 [format.formattable] Status: New Submitter: Hewill Kang Opened: 2025-04-06 Last modified: 2025-04-06

Priority: Not Prioritized

View other active issues in [format.formattable].

View all other issues in [format.formattable].

View all issues with New status.

Discussion:

User-specific formatters usually have the following form:

template <> struct std::formatter<T> {
  constexpr auto parse(format_parse_context& ctx)
    -> format_parse_context::iterator;

  auto format(const T& value, format_context& ctx) const
    -> format_context::iterator;
};

This is reflected in wording examples such as 28.5.6.4 [format.formatter.spec] bullet 8 or 28.5.6.7 [format.context] bullet 9:

#include <format>
#include <string>

enum color { red, green, blue };
const char* color_names[] = { "red", "green", "blue" };

template<> struct std::formatter<color> : std::formatter<const char*> {
  auto format(color c, format_context& ctx) const {
    return formatter<const char*>::format(color_names[c], ctx);
  }
};

which allows us to format color with std::format("{}", red). Unfortunately, even so, the color still does not satisfy std::formattable.

This is because the concept formattable is currently defined as follows:

template<class T, class Context,
         class Formatter = typename Context::template formatter_type<remove_const_t<T>>>
  concept formattable-with =                // exposition only
    semiregular<Formatter> &&
    requires(Formatter& f, const Formatter& cf, T&& t, Context fc,
             basic_format_parse_context<typename Context::char_type> pc)
    {
      { f.parse(pc) } -> same_as<typename decltype(pc)::iterator>;
      { cf.format(t, fc) } -> same_as<typename Context::iterator>;
    };

template<class T, class charT>
  concept formattable =
    formattable-with<remove_reference_t<T>, basic_format_context<fmt-iter-for<charT>, charT>>;

where fmt-iter-for<charT> is an unspecified type that can write charT, which for char is back_insert_iterator<string> and char* in libstdc++ and libc++, respectively.

That is, for color to satisfy formattable, it is necessary to ensure that cf.format(t, fc) is well-formed.

However, the format() function in the above example takes a format_context whose Out parameter is internal iterator type, namely __format::_Sink_iter<char> and back_insert_iterator<__format::__output_buffer<char>> in libstdc++ and libc++, respectively. Since basic_format_context with different Out parameters cannot be converted to each other, the constraint is not satisfied.

The reason color can still be formatted is that basic_format_arg checks for formattable-with<Context> where Context has been correctly specified as format_context.

And since color is formattable but not formattable, this further prevents formatting a range with elements of color, because the formatter specialization for ranges requires that the element type must be formattable. This leads to some inconsistencies (demo):

std::println("{}", red); // ok
static_assert(std::formattable<color, char>); // fires

std::vector<color> v;
std::println("{}", v); // not ok

The workaround is to turn the custom format() into a template function such as format(color c, auto& ctx) or format(color c, basic_format_context<Out, charT>& ctx), However, this seems mandate users to always declare format() as the template function for the best practice, which in my opinion defeats the purpose of introducing format_context in the first place.

Also, since fmt-iter-for<charT> is unspecified, if it is specified in some library implementation as the same type as format_context's Out parameters, then color will suddenly become formattable. This lack of guarantee about formattable can bring unnecessary confusion.

I think we should ensure that color is formattable, because it is formattable.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 28.5.6.3 [format.formattable] as indicated:

    -1- Let fmt-iter-for<charT> be an unspecified type that models output_iterator<const charT&> (24.3.4.10 [iterator.concept.output]).

    […]
    template<class T, class charT>
      concept formattable =
        formattable-with<remove_reference_t<T>, conditional_t<same_as<charT, char>, format_context, wformat_context>basic_format_context<fmt-iter-for<charT>, charT>>;