为什么 std::format() 会在运行时抛出错误的格式说明符?

Wal*_*dal 20 c++ fmt

最近我发现以下代码在几个主要编译器上编译,然后在运行时抛出:

std::cout << std::format("{:*<{}}", 10, "Hello") << std::endl;
Run Code Online (Sandbox Code Playgroud)
terminate called after throwing an instance of 'std::format_error'
  what():  format error: argument used for width or precision must be a non-negative integer
Run Code Online (Sandbox Code Playgroud)

它会抛出异常,因为“10”应该出现在“Hello”之后,而不是之前。

但明显的问题是:为什么它在编译时没有失败?我的理解是,这些参数将在编译时进行类型检查,显然 aconst char*不能用作宽度说明符。为什么这不是编译错误?

如果您不明白为什么这会令人困惑,请知道 的第一个参数std::format()是 type std::format_string<...>此类型在编译时采用字符​​串文字/字符串视图(由于其consteval构造函数),并在编译时读取给定字符串的内容以查看格式参数是否与格式字符串匹配。因此,保证不会std::format("{}");编译,因为字符串“{}”在编译时被读取为格式说明符,但类型列表显示没有传递任何参数,那么该空间中会放入什么?

Wut*_*utz 19

TL;DR:格式说明符内的占位符是 C++20 中编译时验证的一个已知缺点。它正在用 C++26 修复

原答案:

免责声明:这是我刚刚研究后想到的一个想法,我对此并不确定。

我认为编译时验证如何适用于您的情况存在问题。

通常,编译时检查是std::format("{:d}", "hi!")有效的,因为它调用std::formatter<const char*>::parse("{:d}"),即 constexpr ,并且会抛出异常,因为它发现它":d"不适合格式化程序的类型。

您传递此格式字符串:"{:*<{}}"with arg types intthen const char*。所以我们得到以下解析调用:

  • std::formatter<int>::parse("{:*<{}}")- 这是我有点模糊的地方。从界面来看,parse_context我猜测它们只是存储下一个参数的 ID,因此当需要实际格式化内容时,它们可以使用它来获取所需的宽度。但我没有看到parse_context提供一种方法来实际检查下一个参数的类型是什么。所以解析成功了,因为它知道下一个参数可能是一个int.

  • std::formatter<const char*>::parse("{}")- 解析“内部”占位符的格式字符串。这里没什么问题。(编辑3:我不确定这个解析调用是否真的发生。这可能是最外层解析调用的责任来处理其整个格式说明符,包括嵌套占位符。)

这样,解析就成功了,并且只有在运行时,当实际从存储的参数 ID 读取宽度参数时才会发现错误。

编辑:看起来他们实际上通过添加check_dynamic_spec到.C++26 中解决了这个确切的问题parse_context。这样,我想也应该可以检查格式规范中的占位符。

编辑 2:这是介绍这些新方法的论文,并提供了一个与您的非常相似的激励示例。