C++ FMT 问题格式化自定义抽象类

Posted

技术标签:

【中文标题】C++ FMT 问题格式化自定义抽象类【英文标题】:C++ FMT issue formatting a custom abstract class 【发布时间】:2021-12-30 20:22:37 【问题描述】:

我正在为一个个人项目开发一个事件系统,我正在尝试让事件像LOG(event) 一样简单地记录到控制台。

在这种情况下,事件由 Event 类定义,该类具有一些方法和一个 virtual ToString() 函数,该函数返回一个字符串,其中包含事件信息以及我希望在每个事件上输出的任何内容。通过定义继承自 Event 类的特定事件类并根据每个事件的作用和它具有的变量定义 ToString() 函数,进一步扩展了此类。

所以我的LOG 宏调用了一个静态 Log 类的函数,通过将参数转换为字符串来在控制台中显示消息,如下所示:

#define LOG(...) Log::DisplayLog(LogUtils::StringFromArgs(__VA_ARGS__))

这样做是因为DisplayLog() 还接收其他对我的问题不重要的信息参数。

LogUtils::StringFromArgs() 函数通过执行以下操作使用 fmt 将参数转换为字符串:

template<typename FormatString, typename... Args>
inline std::string StringFromArgs(const FormatString& fmt, const Args &... args)

    char arg_string[1024];
    memset(arg_string, 0, sizeof(arg_string));
    fmt::format_to(arg_string, fmt, args...);
    return std::string(arg_string);

所以,当我为此使用 fmt 时,我认为按照 fmt 指南创建事件日志会很容易,我的想法是为事件类:

template<typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<Event, T>::value, char>> : fmt::formatter<std::string>

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

    template<typename FormatCtx>
    auto format(const T& event, FormatCtx& ctx) // also tried Event& instead of T&
    
        return fmt::format_to(ctx.out(), "0", event.ToString());
        // also tried:
        //return fmt::formatter<std::string>::format(event.ToString(), ctx);
    

;

但是,这不起作用,当我尝试执行特定事件类的 LOG(event)(假设“事件”是 WindowResizedEvent 继承自 Event 类)时,我一直从 fmt 遇到相同的错误,因为它无法格式化事件(我也尝试在 Event 类中添加 const char*() 运算符,但仍然相同):

“论点 2”是“事件”论点(正如我所说,我中间还有一些其他论点对这个问题并不重要)。

有谁知道如何在不为每种类型的事件类指定格式化程序的情况下对其进行格式化?因为每种事件类型的代码总是相同的。

【问题讨论】:

LOG(event): 你的宏/函数需要一个格式字符串作为第一个参数。 不,问题出在从 StringFromArgs() 调用的 fmt::format_to() 函数上,不在宏中,宏不需要变量类型 fmt::format_to 的第二个参数必须可转换为格式字符串。您呈现代码的方式,LOG 只是将其第一个参数传递给format_to 的第二个参数。 Event 看起来不是一个有效的格式字符串或不能转换为一个。 嗯,是的,但关键是如何做到这一点(如下回答) fmt 的全部意义在于您可以编写格式字符串,例如"Error at line in object ." 并将 替换为对传递给 format_to 函数的某些对象进行编码的字符串。如果您不需要这样的格式字符串,那么您只需要将运算符转换为std::string 【参考方案1】:

格式字符串应作为fmt::format_string 传递,而不是作为模板参数。这是一个工作示例 (https://godbolt.org/z/xYehaMWsG):

#include <fmt/format.h>

struct Event 
  virtual std::string ToString() const = 0;
;

struct MyEvent : Event 
  std::string ToString() const override 
    return "foo";
  
;

template<typename T>
struct fmt::formatter<
    T, std::enable_if_t<std::is_base_of<Event, T>::value, char>>
    : fmt::formatter<std::string> 
  auto format(const T& event, fmt::format_context& ctx) 
    return fmt::format_to(ctx.out(), "", event.ToString());
  
;

#define LOG(...) fmt::print("", StringFromArgs(__VA_ARGS__))

template <typename... T>
std::string StringFromArgs(fmt::format_string<T...> fmt, T&&... args) 
  return fmt::format(fmt, std::forward<T>(args)...);


int main() 
  LOG("", MyEvent());

请注意,如果要格式化为字符串,最好使用fmt::format 而不是fmt::format_to。事实上,您可以将StringFromArgs 替换为fmt::format

【讨论】:

谢谢!那行得通:) ...此外,有没有办法避免“”参数?因为它需要一个 fmt::format_string 我知道它需要那个字符串,但是我可以用我可以做LOG(event) 的方式来安排它吗?是否有任何 fmt 类型可以让我这样做? 你的意思是重载 LOG 以便它同时接受格式字符串和没有格式字符串的单个参数?如果是,那么由于模棱两可,我不会推荐它。 不,我不是不知道有一种方法可以重载宏...我想知道 fmt 是否支持传递一些通用类型而不是格式字符串,我知道它不会很有道理,或者很笼统,但我对 fmt 的经验很少 也许值得尝试让 DisplayLog() 函数作为模板工作或实际重载该函数

以上是关于C++ FMT 问题格式化自定义抽象类的主要内容,如果未能解决你的问题,请参考以下文章

带有 fmt 的自定义格式说明符用于自定义类

js时间类型的自定义转换库 datetime-fmt

带有外部模板的自定义类型 fmt 格式化程序,有啥缺点吗?

带有编译时格式字符串检查的自定义 fmt 格式化函数

jsp自定义标签(时间格式化包括Long转时间)

如何将自定义格式说明符添加到 fmt