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

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>;
Enjoyed this article?
Check out more content on our blog or follow us on social media.
Browse more articles