【问题标题】:Why C++20 format has no strong typedef for format string?为什么 C++20 格式对格式字符串没有强类型定义?
【发布时间】:2020-11-20 15:51:06
【问题描述】:

C++20 引入了以下format 函数(locale 和 wstring_view 版本被忽略,因为它们不影响问题):

template<class... Args>
std::string format(std::string_view fmt, const Args&... args);

这没有错,但我想知道为什么没有接受“强类型定义”的重载,比如

template<class... Args>
    std::string format(std::format_string fmt, const Args&... args);

我的猜测是以下部分或全部:

  1. 增加了实现的复杂性

  2. 增加编译时间

  3. 代码膨胀

,但我想知道在标准化过程中是否讨论过这个问题。

【问题讨论】:

  • 因为根据您的提议,"Bob" 肯定是一个有效的 format_string,而 "Hi, {}" 是一个有效的 std::string,我能看到这个工作的唯一方法是如果你 禁用 第一次重载并强制人们将他们的格式字符串转换为std::format_string 以调用format。我想您可能会争辩说,使用比格式字符串接受的更多参数调用 std::format 应该是一个错误,但在典型的使用场景下这仍然是一个 runtime 错误。
  • 那么,究竟是什么会阻止"Bob" 以不允许std::format("Bob", "Hi, {}") 的方式转换为std::format_string
  • 如果您依赖显式构造函数来阻止调用std::format("Bob", "Hi, {}"),这意味着您将禁用std::format 的重载,该重载将std::string_view 作为其第一个参数。此时 neither std::format("Bob", "Hi, {}") nor std::format("Hi, {}", "Bob") 是一个有效的调用,因为没有一个std::format_string 作为第一个参数。但如果你真的将它们存储在变量std::format_string fs("Hello {}"); std::string name("Bob"); std::format(name, fs) 中就行不通了?
  • 如果您的目标不是阻止std::format("Bob", "Hello, {}") 编译,那么很难看出它实际上做了什么。如果问题是您自己的函数当前具有签名 foo(std::string, std::string),并且您想将它们重写为 foo(format_string, std::string) 以尝试强制执行顺序,您可以在 std::string 周围编写自己的瘦包装器。
  • @NathanPierson:“我想让用户能够避免在他们的代码中编译这样的代码(如果他们强制使用 format_string)。”但是你'重新建议不会这样做,因为这些都不是format_strings。它们是字符串文字。

标签: c++ c++20 fmt


【解决方案1】:

强类型定义的目的是防止这种情况发生:

void takes_id(SomeIdType);
takes_id(42); 

format 的目的是让它工作:

format("User {} owes me {} points.", name, 100);

这是一个字符串文字。需要一个强类型意味着用户的负担更大,不得不写这样的东西:

format(format_string("User {} owes me {} points."), name, 100);

这不是典型的强 typedef 用例的负担,因为您实际上是在贩卖SomeIdTypes。您将拥有一个为您提供SomeIdType 的函数,您将存储SomeIdType 类型的成员。基本上,实际转换的数量会相当少……所以在调用站点上,您只需编写takes_id(my_id),代码大部分看起来都一样,但增加了安全性。

但最常见的格式化案例是使用字符串字面量,因此需要添加很多注释。

强类型的名义上的好处是捕捉用户可能会做这样的事情:

format(name, "User {} owes me {} points.", 100);

甚至:

format(name, 100);

前者似乎不太可能发生。如果第一个参数恰好足够像字符串,则后者当然是可能的。但这是否是一个足够普遍的问题,以至于迫使每个人都编写更多的代码?我不这么认为。

现在,如果字符串文字有自己与 const char[N] 不同的类型(我真的希望他们这样做),那么就有可能创建一个可以从 std::string_literal 隐式构造的类型,但需要是 std::string_view 显式构造。如果这是一件事,那么 API 可能会使用它 - 因为这在常见情况下不需要注释,并且不使用字符串文字似乎非常罕见,以至于需要显式转换似乎......很好?

此外,在安全问题上,问题不在于传递错误类型的字符串,而在于实际上能够在其上下文中验证它的值:

format("User {} owes me {} points.", name);

我们真的希望这个不要编译,即使我们在正确的位置提供了一个格式字符串。它似乎是possible to do this。但是我们也不需要强类型定义,我们只需要知道格式字符串是否为常量表达式的能力。


总而言之,答案是:

但我想知道为什么没有接受“强类型定义”的重载

是这需要用户提供更多的调用端注释,同时提供非常小的好处。它只会在最稀有的用途中发现错误的用途,因此似乎是一个相当糟糕的权衡。

【讨论】:

  • 不错的答案,但只是为了澄清一下,因为我的 q 可能对此不够清楚:我不想删除 string_view 重载,我只是想知道为什么 format_string 重载不存在。
  • @NoSenseEtAl 如果 format_string 重载确实存在,它不会阻止任何人调用 string_view 重载。
  • 试图获得超级漂亮的 Twitter 链接 Godboltified,不确定我是否在宏定义上失败,或者“trunk”的 Godbolt 版本太旧...godbolt.org/z/jxPfhzgithub.com/fmtlib/fmt/search?l=C%2B%2B&q=VERSION
  • @NoSenseEtAl 我在一开始就问过你是否是指可以在fmt 库中执行的编译时参数有效性检查,你说不,你在谈论不同的东西。并注意这些检查的局限性:godbolt.org/z/bd5oxc 尽管在编译时显然无法知道 fmt_string 是否有效,但编译起来非常高兴。
  • @NathanPierson 我正在回复 Barry 在他的回答中提供的链接......它与问题没有直接关系。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-06
  • 1970-01-01
相关资源
最近更新 更多