为什么 std::ssize 被强制为其有符号大小类型的最小大小

Why is std::ssize being forced to a minimum size for its signed size type?

本文关键字:类型 小类 小大 符号 ssize std 为什么      更新时间:2023-10-16

在C++20中,引入了std::ssize来获取通用代码容器的符号大小。 (这里解释了添加它的原因。

有点奇特的是,那里给出的定义(与common_typeptrdiff_t相结合)具有强制返回值为"容器size()返回值的ptrdiff_t或签名形式,以较大者为准"的效果。

P1227R1间接地为此提供了理由(">对于 std::ssize() 来说,将 60,000 的大小变成 -5,536 的大小将是一场灾难")。

然而,在我看来,这是一种试图"修复"这个问题的奇怪方法。

  • 有意定义uint16_t大小且已知永远不会超过 32,767 个元素的容器仍将被迫使用比所需更大的类型。
    • 对于分别使用uint8_t大小和 127 个元素的容器,也会发生同样的事情。
    • 在桌面环境中,您可能并不关心;但这对于嵌入式或其他资源受限的环境可能很重要,特别是如果结果类型用于比堆栈变量更持久的东西。
  • 在 32 位平台上使用默认size_t大小但包含 2B 和 4B 项的容器会遇到与上述完全相同的问题。
  • 如果仍然存在ptrdiff_t小于 32 位的平台,它们也会遇到相同的问题。

按原样使用有符号类型(不扩展其大小)并assert没有发生转换错误(例如,结果不是负数)不是更好吗?

我错过了什么吗?


稍微扩展一下最后一个建议(灵感来自 Nicol Bolas 的回答):如果它按照我建议的方式实现,那么这段代码就可以工作™了:

void DoSomething(int16_t i, T const& item);
for (int16_t i = 0, len = std::ssize(rng); i < len; ++i)
{
DoSomething(i, rng[i]);
}

然而,在当前的实现中,这会产生警告和/或错误,除非明确添加static_cast以缩小ssize的结果,或者改用int i,然后在函数调用(和范围索引)中缩小它,这两者都不像是改进。

有意

定义uint16_t大小且已知永远不会超过 32,767 个元素的容器仍将被迫使用比所需更大的类型。

这不像容器将大小存储为此类型。转换是通过访问值进行的。

至于嵌入式系统,嵌入式系统程序员已经知道C++倾向于增加小型系统的大小。因此,如果他们希望一个类型是int16_t,他们将在代码中详细说明,否则C++可能只是将其提升为int

此外,没有标准的方法可以询问一个范围"已知永远不会超过"的大小。decltype(size(range))是你可以要求的;大小范围不需要提供max_size功能。如果没有这种能力,最安全的假设是大小类型为uint16_t的范围可以假定该范围内的任何大小。因此,有符号大小应该足够大,以便将整个范围存储为有符号值。

您的建议基本上是任何ssize调用都可能是不安全的,因为任何size范围的一半都无法有效地存储在返回类型的ssize中。

在 32 位平台上使用默认size_t大小但包含 2B 到 4B 项的容器会遇到与上述完全相同的问题。

假设ptrdiff_t在此类平台上不是有符号的 64 位整数是有效的,那么这个问题并没有真正的有效解决方案。所以是的,在某些情况下,ssize可能是不安全的。

ssize目前在不可能安全的情况下可能是不安全的。 您的建议将使ssize在所有情况下都可能不安全。

这不是一个进步。

不,仅仅断言/合同检查不是一个可行的解决方案。ssize的重点是使for(int i = 0; i < std::ssize(rng); ++i)工作,而不会让编译器抱怨有符号/无符号不匹配。由于不需要发生的转换失败而获得断言(顺便说一句,如果不使用std::size就无法纠正,我们试图避免),最终与您的算法无关?这是一个糟糕的主意。


如果它按照我建议的方式实现,那么这段代码就可以工作™了:

让我们忽略用户编写此代码的频率问题。

编译器期望/要求您在那里使用强制转换的原因是,您请求的是一个本质上危险的操作:您可能会丢失数据。只有当当前大小适合int16_t时,您的代码才"正常工作™";这使得转换在静态上是危险的。这不是应该隐式发生的事情,因此编译器建议/要求您显式请求它。用户查看该代码时会得到一个又大又胖的眼睛,提醒他们正在做一件危险的事情。

这都是好事。

看,如果你建议的实现是ssize的行为方式,那么这意味着我们必须将ssize的每一次使用视为与编译器对待你尝试的隐式转换一样具有内在的危险性。但与static_cast不同的是,ssize很小,很容易错过。

危险操作应该这样称呼。由于ssize很小并且设计难以注意到,因此它应该尽可能安全。理想情况下,它应该像size一样安全,但做不到这一点,它应该不安全,只是在不可能使其安全的情况下。

用户不应该将ssize的使用视为可疑或令人不安的东西;他们不应该害怕使用它。