fmt::formatter 专业化不理解

Mic*_*l A 1 c++ c++20 fmt

我正在使用一种选择加入方法,可以通过自定义类的 std::cout 和 fmt::print 打印到控制台。为此,我创建了一个std::string to_string(const T& value)在一般情况下未定义的函数。专业课程应该:

  1. 专门化to_string(const MyType& t)
  2. 专门化struct printable< MyType >: public std::true_type{}

这将依次激活std::ostreamfmt::formatter自动专门针对每种printable类型。一个完整的例子是这样的:

#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{});
}

Run Code Online (Sandbox Code Playgroud)

然而,对于fmtv10.1.1,这会带来一些神秘的错误消息 (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>{}),
Run Code Online (Sandbox Code Playgroud)

<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{});
Run Code Online (Sandbox Code Playgroud)

该示例可以在 godbolt上找到。

为什么我的格式化程序方法不被接受,我怎样才能让 fmt 理解它?

Bar*_*rry 6

MyClass是一个范围(它有begin()end()返回迭代器)。fmt对于所有范围都有一个默认格式化程序,这与您尝试添加的格式化程序冲突。两者都不比另一个更专业,所以它是模棱两可的。

如果您禁用范围格式化程序,则只会使用您的范围格式化程序:

template <typename Char>
struct fmt::range_format_kind<MyClass, Char>
    : std::integral_constant<fmt::range_format, fmt::range_format::disabled>
{ };
Run Code Online (Sandbox Code Playgroud)

不过,一旦你这样做了,你的实际定制to_string就不太正确了(你会得到一个未定义的引用,common::to_string<MyClass>因为你的函数不会被考虑),但这是一个单独的问题。