无法弄清楚如何在 CRTP 模板上正确地专门化 fmt 模板 类

can't figure out how to properly specialize fmt template on CRTP template classes

(注意:ostream 不是一个选项,不建议将重载 ostream 运算符作为解决方案) (注意:显示每次尝试的实际工作测试代码位于底部)

基本上我有一个 class 继承权,看起来像这样:

template<typename T, typename value_type>
class CRTPBase {
    using CRTPBase_value = value_type;
    ...

}

template<typename T> 
class derived_1: public CRTPBase<derived_1, derived_type_1>{
...
}

template<typename T> 
class derived_2: public CRTPBase<derived_2, derived_type_2>{
...
}

template<typename T> 
class derived_3: public CRTPBase<derived_3, derived_type_3>{
...
}

template<typename T> 
class derived_4: public CRTPBase<derived_4, derived_type_4>{
...
}

我想为每个派生的 class 重载 fmt::format,而不是为每个重复代码,因为为每种类型打印的实际代码将完全相同。

我首先尝试仅使用一个派生模板进行测试 classes 是专门化的

template <typename T>
struct fmt::formatter<derived_1<T>>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const derived_1<T>& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, ", ", v(i,j));
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }

};

这非常适合

derived_1 d = ...; 

fmt::print("{}", d);

然后我尝试看看我是否可以以某种方式使用 CRTP 基础来专门化

template <typename T>
struct fmt::formatter<CRTPBase<T, T::derived_type>>{
    template <typename FormatContext>
    auto format(const CRTPBase<T, T::derived_type>& v, FormatContext& ctx)
}

这不起作用,我得到“静态断言失败:不知道如何格式化类型”,两种类型之间没有有效的转换,我猜这是因为显式模板转换问题。那我试试

template <typename T>
struct fmt::formatter<CRTPBase<T, T::derived_type>>{
    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
}

它似乎跳过了这个,我得到“静态断言失败:不知道如何格式化类型”,我猜是因为 fmt 内部使用的任何机制都需要存在实际的格式化程序类型才能工作.

然后我尝试使用 SFINAE 看看我是否可以做同样的事情

我的 is_CRTP_derived class 看起来像:

template<class Derived_T>
using derived_element_wise = decltype(Derived_T::CRTPBase_value);

template<class Derived_T>
using is_CRTP_derived = std::experimental::is_detected<derived_element_wise, Derived_T>

第一次尝试是这样的:

template <typename T, typename U = enable_if_t<is_CRTP_derived<T>::value>>
struct fmt::formatter<T>{
    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
}

我收到有关“模板参数在部分专业化中不可推导:”的错误消息,好的,所以这不起作用接下来我尝试

template <typename T>
struct fmt::formatter<T,enable_if_t<is_CRTP_derived<T>::value>>{
    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
}

似乎完全跳过了类型,我得到“静态断言失败:不知道如何格式化类型”。所以我尝试

template <typename T>
struct fmt::formatter<enable_if_t<is_CRTP_derived<T>::value, T>>{
    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
}

我得到“错误:模板参数在部分特化中不可推导”然后我尝试

template <typename T>
struct fmt::formatter<T>{
    using temp = enable_if_t<is_CRTP_derived<T>::value>;
    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
}

并且出现错误:'struct fmt::v5::formatter' 的声明混淆了 'struct fmt::v5::formatter'.

的早期模板实例化

那就是我放弃了。我不知道有什么其他方法可以真正让它发挥作用如何避免对每个 CRTP 派生模板执行此操作 class?

编辑:

这是演示该问题的实际工作代码:

//main.cpp

#include <fmt/format.h>
#include <fmt/core.h>
#include <experimental/type_traits>
#include <type_traits>
//change 0->6 to see errors of each attempt
#define ATTEMPT 0

template<class Derived_T>
using derived_element_wise = decltype(Derived_T::CRTPBase_value);

template<class Derived_T>
using is_CRTP_derived = std::experimental::is_detected<derived_element_wise, Derived_T>;

template<typename T, typename value_type>
struct CRTPBase {
    using CRTPBase_value = value_type;
    std::size_t size(){
        auto derived = static_cast<const T*>(this);
        return derived->width() * derived->height();
    }
};

template<typename T>
struct derived_1: public CRTPBase<derived_1<T>, typename std::remove_const<T>::type>{
    using derived_type = T;
    std::size_t width() const{
        return 1;
    }
    std::size_t height() const{
        return 1;
    }
};

template<typename T>
struct derived_2: public CRTPBase<derived_2<T>, typename std::remove_const<T>::type>{
    using derived_type = T;
    std::size_t width() const{
        return 2;
    }
    std::size_t height() const{
        return 2;
    }
};

template<typename T>
struct derived_3: public CRTPBase<derived_3<T>, typename std::remove_const<T>::type>{
    using derived_type = T;
    std::size_t width() const{
        return 3;
    }
    std::size_t height() const{
        return 3;
    }
};

template<typename T>
struct derived_4: public CRTPBase<derived_4<T>, typename std::remove_const<T>::type>{
    using derived_type = T;
    std::size_t width() const{
        return 4;
    }
    std::size_t height() const{
        return 4;
    }
};



#if ATTEMPT == 0
// Example properly working printer
template <typename T>
struct fmt::formatter<derived_1<T>>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const derived_1<T>& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};

template <typename T>
struct fmt::formatter<derived_2<T>>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const derived_2<T>& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};


template <typename T>
struct fmt::formatter<derived_3<T>>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const derived_3<T>& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};

template <typename T>
struct fmt::formatter<derived_4<T>>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const derived_4<T>& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};
#elif ATTEMPT == 1
template <typename T>
struct fmt::formatter<CRTPBase<T, typename T::derived_type>>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const CRTPBase<T, typename T::derived_type>& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};


#elif ATTEMPT == 2
template <typename T>
struct fmt::formatter<CRTPBase<T, typename T::derived_type>>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};
#elif ATTEMPT == 3
template <typename T, typename U = std::enable_if_t<is_CRTP_derived<T>::value>>
struct fmt::formatter<T>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};
#elif ATTEMPT == 4
template <typename T>
struct fmt::formatter<T,std::enable_if_t<is_CRTP_derived<T>::value>>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};
#elif ATTEMPT == 5
template <typename T>
struct fmt::formatter<std::enable_if_t<is_CRTP_derived<T>::value, T>>{

    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};
#elif ATTEMPT == 6
template <typename T>
struct fmt::formatter<T>{
    using temp = std::enable_if_t<is_CRTP_derived<T>::value>;
    template <typename ParseContext>
    constexpr auto parse(ParseContext& ctx){
        return begin(ctx);
    }

    template <typename FormatContext>
    auto format(const T& v, FormatContext& ctx)
    {
        auto&& out= ctx.out();
        if(v.height() > 1){
            format_to(out, "[\n");
        }
        for(std::size_t i = 0; i < v.height(); ++i){
            format_to(out, "[");
            for(std::size_t j = 0; j < v.width(); ++j){
                format_to(out, "{},", 0);
            }
            format_to(out, "]\n");
        }
        if(v.height() > 1){
            return format_to(out, "]\n");
        }
        return out;
    }
};
#endif

int main(){
    derived_1<int> d1;
    derived_2<float> d2;
    derived_3<bool> d3;
    derived_4<double> d4;

    fmt::print("{}", d1);
    fmt::print("{}", d2);
    fmt::print("{}", d3);
    fmt::print("{}", d4);

    return 0;
}

您可以使用 SFINAE 来做到这一点:

template <typename T>
struct fmt::formatter<
  T, std::enable_if_t<
       std::is_base_of_v<CRTPBase<T, typename T::derived_type>, T>, char>> {

  auto parse(format_parse_context& ctx) { return ctx.begin();}

  template <typename FormatContext>
  auto format(const T& v, FormatContext& ctx) {
    // Format v and write the output to ctx.out().
    return ctx.out();
  }
};

这是 Godbolt 上的完整工作示例:https://godbolt.org/z/vsbcc8

在 {fmt} 文档中也有一个这样做的例子:https://fmt.dev/latest/api.html#formatting-user-defined-types