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

Mir*_*ral 6 c++ language-lawyer c++20

In C++20, std::ssize is being introduced to obtain the signed size of a container for generic code. (And the reason for its addition is explained here.)

Somewhat peculiarly, the definition given there (combining with common_type and ptr_diff_t) has the effect of forcing the return value to be "either ptrdiff_t or the signed form of the container's size() return value, whichever is larger".

P1227R1 indirectly offers a justification for this ("it would be a disaster for std::ssize() to turn a size of 60,000 into a size of -5,536").

This seems to me like an odd way to try to "fix" that, however.

  • Containers which intentionally define a uint16_t size and are known to never exceed 32,767 elements will still be forced to use a larger type than required.
    • Likewise for containers using a uint8_t size and 127 elements, respectively.
    • In desktop environments, you probably don't care; but this might be important for embedded or otherwise resource-constrained environments, especially if the resulting type is used for something more persistent than a stack variable.
  • Containers which use the default size_t size on 32-bit platforms but which nevertheless do contain between 2B and 4B items will hit exactly the same problem as above.
  • If there still exist platforms for which ptrdiff_t is smaller than 32 bits, they will hit the same problem as well.

Wouldn't it be better to just use the signed type as-is (without extending its size) and to assert that a conversion error has not occurred (eg. that the result is not negative)?

Am I missing something?


To expand on that last suggestion a bit (inspired by Nicol Bolas' answer): if it were implemented the way that I suggested, then this code would Just Work™:

void DoSomething(int16_t i, T const& item);

for (int16_t i = 0, len = std::ssize(rng); i < len; ++i)
{
    DoSomething(i, rng[i]);
}
Run Code Online (Sandbox Code Playgroud)

With the current implementation, however, this produces warnings and/or errors unless static_casts are explicitly added to narrow the result of ssize, or to use int i instead and then narrow it in the function call (and the range indexing), neither of which seem like an improvement.

Nic*_*las 3

\n

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

\n
\n\n

这不像容器存储这种类型的大小。转换是通过访问值发生的。

\n\n

对于嵌入式系统,嵌入式系统程序员已经了解 C++ 增加小类型大小的倾向。因此,如果他们期望类型为int16_t,他们将在代码中拼写出来,因为否则 C++ 可能会将其提升为int

\n\n

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

\n\n

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

\n\n
\n

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

\n
\n\n

假设ptrdiff_t在此类平台上非有符号 64 位整数是有效的,则该问题实际上没有有效的解决方案。所以,是的,存在ssize潜在不安全的情况。

\n\n

ssize当前在不可能安全的情况下可能不安全。您的建议ssize所有情况下都可能不安全。

\n\n

这不是一个进步。

\n\n

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

\n\n
\n\n
\n

如果按照我建议的方式实现,那么此代码将正常工作\xe2\x84\xa2:

\n
\n\n

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

\n\n

您的编译器期望/要求您在那里使用强制转换的原因是因为您要求进行本质上危险的操作:您可能会丢失数据。如果当前大小适合int16_t; ,则您的代码仅“Just Works\xe2\x84\xa2” 这使得转换具有静态危险。这不是应该隐式发生的事情,因此编译器建议/要求您显式请求它。看到这些代码的用户会感到很眼花缭乱,提醒他们正在做一件危险的事情。

\n\n

这都是好事。

\n\n

看看,如果您建议的实现是如何ssize表现的,那么这意味着我们必须将每次使用ssize视为本质上危险的,就像编译器对待您尝试的隐式转换一样。但与 不同的是static_castssize它很小,很容易被错过。

\n\n

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

\n\n

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

\n