在实现自定义类型的格式化程序时,如何有效地转发格式化参数



我正在尝试编写一个自定义格式化程序来帮助打印矢量。我试图维护格式说明符,以便向量中的每个项都以相同的方式格式化。

我从本教程和文档中获得了大部分灵感

#include <fmt/core.h>
#include <fmt/format.h>
template<typename ValueType>
struct fmt::formatter<std::vector<ValueType>> : fmt::formatter<ValueType>
{
std::string formatString;
// Hack, copy the original format text into a std::string
constexpr auto parse(format_parse_context& ctx)
{
formatString= "{:";
for (auto iter = std::begin(ctx); iter  != std::end(ctx); ++iter) {
char c = *iter;{
formatString += c;
}
if (c == '}') {
return iter;
}
}
return std::end(ctx);
}
template <typename FormatContext>
auto format(const std::vector<ValueType>& container, FormatContext& context)
{
auto&& out = context.out();
format_to(out, "{{");
typename std::vector<ValueType>::size_type count = 0;
const typename std::vector<ValueType>::size_type size = container.size();
for (const auto& item : container) {
// Use the copied format string, but really want to delegate the formatting to superclass...
format_to(out, formatString, item);
if (++count < size) {
format_to(out, ", ");
}
}
return format_to(out, "}}");
}
};
int main()
{
fmt::print("{:.3f}n", std::vector{ 0.0, 321.123, 654398.4328, -0.0000000000012345, 2374651273.7236457862345});
fmt::print("{:.1e}n", std::vector{ 0.0, 321.123, 654398.4328, -0.0000000000012345, 2374651273.7236457862345});
return 0;
}

哪个输出:

{0.000, 321.123, 654398.433, -0.000, 2374651273.724}
{0.0e+00, 3.2e+02, 6.5e+05, -1.2e-12, 2.4e+09}

复制格式字符串只是为了将其反馈到另一个fmt::format调用中,这似乎过于笨拙和低效,尤其是当扩展类: fmt::formatter<ValueType>已经在内部为我们提供了一个完全有效的parse函数时(我在本例中重新实现了它,只是为了以一种巧妙的方式获得所需的输出(。

我真的想删除自定义解析实现,并替换行

format_to(out, formatString, item);

带有

format_to(out, fmt::formatter<ValueType>::format(item, context))

除非它无效/不编译。

正确的方法是什么?


注意:我完全意识到在我的例子中扩展类型的毫无意义,我可以将它作为一个局部变量,但我正在尝试重用类的功能,所以扩展它感觉是正确的方向,即使我还没有找到解决方案。


我发现的所有其他对我没有帮助的例子的列表:

  • 用C++20 std::format((以很酷的方式格式化字符串(仅适用于直接返回对超级实现的单个调用,但不需要重新实现解析或创建新的格式字符串(
  • 自定义类的带有{fmt}的自定义格式说明符(放弃格式设置或需要手动重新实现格式设置(
  • 我主要复制的教程(几乎是我想要的,但仍然创建一个新的格式字符串,而不是重复使用用户最初指定的字符串(
  • Docs(同样,建议重新实现解析函数,并使用新的格式字符串调用format,尽管调用者已经指定了一个格式字符串,并且该类型已有一个解析函数…(

您可以去掉parse实现并使用继承的函数,并在format中使用fmt::formatter<ValueType>::format(item, context)来输出每个项(godbolt演示(:

#include <fmt/core.h>
#include <fmt/format.h>
template<typename ValueType>
struct fmt::formatter<std::vector<ValueType>> : fmt::formatter<ValueType>
{
template <typename FormatContext>
auto format(const std::vector<ValueType>& container, FormatContext& context)
{
auto&& out = context.out();
format_to(out, "{{");
bool first = true;
for (const auto& item : container) {
if (first) {
first = false;
} else {
format_to(out, ", ");
}
fmt::formatter<ValueType>::format(item, context);
}
return format_to(out, "}}");
}
};
int main()
{
fmt::print("{:.3f}n", std::vector{ 0.0, 321.123, 654398.4328, -0.0000000000012345, 2374651273.7236457862345});
fmt::print("{:.1e}n", std::vector{ 0.0, 321.123, 654398.4328, -0.0000000000012345, 2374651273.7236457862345});
return 0;
}

您可以在格式化用户定义类型下的文档中看到此模式的其他示例。

最新更新