I am using an opt-in method for enabling printing to the console via std::cout and fmt::print of custom classes. To do so I create a std::string to_string(const T& value)
function that is left undefined in the general case. Specializing classes are supposed to:
to_string(const MyType& t)
struct printable< MyType >: public std::true_type{}
This will in turn activate std::ostream
and fmt::formatter
which specialize automatically for every printable
type. A complete example is this:
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
#include <concepts>
#include <iostream>
#include <string>
#include <vector>
namespace common {
template <typename T>
std::string to_string(const T& value);
template <typename T>
struct printable : std::false_type {};
template <typename T>
constexpr bool printable_v = printable<T>::value;
} // namespace common
template <typename T>
requires(common::printable_v<T>)
auto& operator<<(std::ostream& os, const T& value) {
return os << common::to_string(value);
}
template <class T>
requires(common::printable_v<T>)
struct fmt::formatter<T> : public fmt::ostream_formatter {};
struct MyClass {
std::vector<int> v{1, 2, 3, 4, 5};
auto begin() { return v.begin(); }
auto begin() const { return v.begin(); }
auto end() { return v.end(); }
auto end() const { return v.end(); }
};
namespace common {
inline std::string to_string(const MyClass& c) {
return fmt::format("{}", c.v);
}
template <>
struct printable<MyClass> : std::true_type {};
} // namespace common
int main() {
// this works:
// std::cout << common::to_string(MyClass{});
// this doesn't:
fmt::format("{}", MyClass{});
}
However, for fmt
v10.1.1 this brings up somewhat cryptic error messages (clang-17):
/opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/type_traits:1048:21: error: static assertion failed due to requirement 'std::__is_complete_or_unbounded(std::__type_identity<fmt::formatter<MyClass, char, void>>{})': template argument must be a complete class or an unbounded array
1048 | static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{}),
and
<source>:55:17: error: call to consteval function 'fmt::basic_format_string<char, MyClass>::basic_format_string<char[3], 0>' is not a constant expression
55 | fmt::format("{}", MyClass{});
The example can be found here on godbolt.
Why isn't my formatter method accepted and how can I make fmt understand it?
MyClass
is a range (it has begin()
and end()
which return iterators). fmt
has a default formatter for all ranges, which conflicts with the formatter you're trying to add. Neither is more specialized than the other, so it's ambiguous.
If you disable the range formatter, then only yours will be used:
template <typename Char>
struct fmt::range_format_kind<MyClass, Char>
: std::integral_constant<fmt::range_format, fmt::range_format::disabled>
{ };
Once you do that, though, your actual customization for to_string
isn't quite right (you'll get an undefined reference to common::to_string<MyClass>
since your function won't be considered), but that's a separate problem.