为什么 std::ssize 被强制为其有符号大小类型的最小大小
Why is std::ssize being forced to a minimum size for its signed size type?
在C++20中,引入了std::ssize来获取通用代码容器的符号大小。 (这里解释了添加它的原因。
有点奇特的是,那里给出的定义(与common_type
和ptrdiff_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
的使用视为可疑或令人不安的东西;他们不应该害怕使用它。
- 调用具有未标识类型的类的方法
- C++ 中模板化类型的类层次结构
- 根据模板类型选择类模板的成员类型?
- C++从抽象类型定义类成员
- 实例化具有不完整类型的类模板格式不正确(如果该类型是在之后定义的)
- 重载模板<类型名...>类的函数模板
- 在设计方面:重载vector类型的类成员的插入运算符
- 错误:使用 SWIG 的未知类型名称"类"
- 添加字符串类型的类成员会导致调用基类函数而不是子函数
- 包装任意类型/非类型模板类的模板类
- 使用相同方法但不同成员类型构建类的最佳方法
- 如何删除类内类类型的类成员指针
- 为什么我不能在同一行中定义两个相同类型的类的成员指针
- 如何仅从类类型推断类构造函数参数的类型
- 为什么要返回对小类成员的 const 引用?
- (C++);动态决定函数的类型(派生类)
- 具有容器变量类型的类模板
- 如何基于模板参数类型施放类模板
- 包含仅移动类型的类的构造函数应通过引用还是通过右值引用接收仅移动类型?
- 无法将类型为"类名 &"的非常量左值引用绑定到类型为"类名"的右值