std::format handles user-defined type if it's iterable‽

std::format handles user-defined type if it's iterable‽
typescript
Ethan Jackson

I updated some older code to use std::format, and was surprised to discover that it worked despite that fact that I had forgotten to provide a std::formatter specialization for that type.

I immediately made a small test program to try to reproduce this, but those always got a compile-time error as I expected.

After hours of debugging, I figured out that, if the custom type has public begin and end methods, the library will format the sequence as a comma-separated list enclosed in square brackets.

Q: Is this a standards-defined feature of std::format or an implementation bug? (Or something else?)

Here's a self-contained repro:

#include <array> #include <print> class MyType { public: MyType() : m_values{1, 2, 3, 4} {} using internal_type = std::array<int, 4>; using const_iterator = typename internal_type::const_iterator; const_iterator cbegin() const { return m_values.cbegin(); } const_iterator cend() const { return m_values.cend(); } const_iterator begin() const { return cbegin(); } const_iterator end() const { return cend(); } private: internal_type m_values; }; int main() { MyType foo; // Since MyType is a user-defined type, I would not // expect this print statement to compile without a // specialization of std::formatter, but because // it's iterable, it prints: "foo = [1, 2, 3, 4]\n". std::print("foo = {}\n", foo); return 0; }

I'm using MS VC++ from Visual Studio 17.12.15 and compiling with /std:c++latest.

Answer

The standard library defines a std::formatter specialization for ranges starting in C++23:

template< ranges::input_range R, class CharT > requires (std::format_kind<R> != std::range_format::disabled) && std::formattable<ranges::range_reference_t<R>, CharT> struct formatter<R, CharT>;

Related Articles