问题描述
(注意:ostream 不是一个选项,不建议重载 ostream 运算符作为解决方案) (注意:显示每次尝试的实际工作测试代码位于底部)
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>{
...
}
我想为每个派生类重载 fmt::format,而不是为每个类重复代码,因为要为每种类型打印的实际代码完全相同。
我首先尝试仅使用一个特化的派生模板类进行测试
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 类看起来像:
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 派生模板类执行此操作?
编辑:
以下是演示问题的实际工作代码:
//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,"{},0);
}
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,"]\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,"]\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,"]\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,"]\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,"]\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,"]\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,"]\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,"]\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,"]\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。