为什么 C++ 数组索引值是有符号的而不是围绕 size_t 类型构建的(或者我错了)?

Why are C++ array index values signed and not built around the size_t type (or am I wrong in that)?

我越来越难跟上不断发展的 C++ 标准,但现在对我来说很清楚的一件事是数组索引值应该是整数(不是 long longsize_t 或其他一些看似更合适的 size 的选择)。我从这个问题的答案 (Type of array index in C++) 以及成熟的 C++ 库(如 Qt)使用的实践中推测了这一点,这些库也使用简单的整数来表示大小和数组索引运算符。对我来说,棺材上的钉子是我现在从 MSVC 2017 收到大量编译器警告,指出我的 const unsigned long long(又名 const size_t)变量正在用作数组索引时隐式转换为类型 const int

Mat 在上面链接的问题中给出的答案引用了 ISO C++ 标准草案 n3290 的说法

it shall be an integral constant expression and its value shall be greater than zero.

我没有阅读这些规范和准确解释他们语言的背景,所以可能需要澄清几点:

如果我在这里看到的一切都是真的,数组索引值应该是 signed int 类型,为什么?这对我来说似乎违反直觉。规范甚至声明表达式 "shall be greater than zero" 所以如果它是 signed 我们就有点浪费了。当然,我们仍然可能希望以某种方式将索引与 0 进行比较,这对于 unsigned 类型是危险的,但是应该有更便宜的方法来解决这个问题,只浪费一个值,而不是一个整个位。

此外,随着寄存器的不断扩大,一个更具前瞻性的解决方案是允许更大的索引类型(如 long long),而不是坚持使用 int,这是一个历史上有问题的类型无论如何(当处理器更改为 32 bits 时更改其大小,然后当它们转到 64 bits 时则不更改)。我什至看到一些人在传闻中谈论 size_t,就像它被设计成一种更适合未来使用的类型(而不仅仅是 sizeof 运算符服务中返回的类型)。但当然,这可能是杜撰的。

我只是想确保我对这里的基础编程理解没有缺陷。当我看到像 ISO C++ 组这样的专家或 Qt 的工程师在做某事时,我相信他们有充分的理由!对于像数组索引这样的编程基础,我觉得我需要知道原因是什么,否则我可能会遗漏一些重要的东西。

我认为标准库 API 更喜欢索引是无符号类型。如果您查看 std::size_t 的文档,它会指出

When indexing C++ containers, such as std::string, std::vector, etc, the appropriate type is the member typedef size_type provided by such containers. It is usually defined as a synonym for std::size_t.

在查看 std::vector::at

等函数的签名时,这一点得到了加强
reference       at( size_type pos );
const_reference at( size_type pos ) const;

我认为你混淆了两种类型:

  1. 第一种是object/value类型,可以用来定义数组的大小。不幸的是,你 link 在他们应该使用 array size 的地方使用 index 的问题。这必须是一个必须在编译时求值的表达式,并且它的值必须大于零。

    int array[SomeExpression]; // Valid as long as SomeExpression can be evaluated 
                               // at compile time and the value is greater than zero.
    
  2. 第二种是object/value的类型,可以用来访问数组。鉴于以上 array,

    array[i] = SomeValue; // i is an index to access the array
    

    i不需要在编译时求值,i必须在[0, SomeExpression-1]范围内。但是,可以使用负值作为索引来访问数组。由于 array[i] 被评估为 *(array+i) (暂时忽略重载的 operator[] 函数),如果 array 恰好指向 i 可以是负值一个数组的中间。 My answer 到另一个 SO post 有关于这个主题的更多信息。

    顺便说一句,由于array[i]被计算为*(array+i),因此使用i[array]是合法的,与array[i]相同。

看看 [expr.sub]/1 我们有

A postfix expression followed by an expression in square brackets is a postfix expression. One of the expressions shall be a glvalue of type “array of T” or a prvalue of type “pointer to T” and the other shall be a prvalue of unscoped enumeration or integral type. The result is of type “T”. The type “T” shall be a completely-defined object type.67 The expression E1[E2] is identical (by definition) to *((E1)+(E2)), except that in the case of an array operand, the result is an lvalue if that operand is an lvalue and an xvalue otherwise. The expression E1 is sequenced before the expression E2.

强调我的

因此,下标运算符的索引需要是无作用域的枚举或整数类型。查看 [basic.fundamental],我们看到标准整数类型是 signed charshort intintlong intlong long int,以及它们的无符号对应类型。

因此任何标准整数类型都可以工作,任何其他整数类型,如 size_t,都将是用作数组索引的有效类型。提供给下标运算符的值甚至可以是负值,只要该值可以访问有效元素即可。