(以下所有标准参考均指N4659:2017年3月后Kona工作草案/C++17 DIS,在标准报价中强调我的)
<小时 />背景
我试图回答命名变量是否arr
下面的问题
extern int arr[]; // declaration; type is int[]
int arr[10]; // definition; type is int[10]
可转移到单个实体的类型,这样该实体具有不同的类型,具体取决于它被引用的上下文(1),或者实体的类型是否始终相同,并且只是变量的类型(其名称表示实体)因上下文而异。
(1) 据我所知,唯一适用于这种情况的其他情况是枚举器(它们是实体),它们具有不同的类型,具体取决于它们是从定义它们的枚举内部引用的(类型是枚举的基础类型)还是从外部引用(类型是枚举的类型)。
问题
extern
变量声明是否也声明一个实体(表示实体的变量名称),以便该实体具有不同的类型,具体取决于其extern
变量声明引用还是其重新声明和定义(int[]
与上面的int[10]
)?- 或者,实体的类型是否始终由对象定义(
int[10]
上文)指定,而类型的差异只是由于变量类型的差异取决于我们引用它的上下文?
详
[basic]/3 将实体定义为:
实体是值、对象、引用、函数、枚举器、类型、类成员、位字段、模板、模板专用化、命名空间或参数包。
[dcl.stc]/5 介绍了extern
说明符,以及如何将其应用于声明变量:
extern
说明符应仅适用于变量或函数的声明。[...]
[basic]/6 将变量定义为:
变量是通过声明非静态数据成员或对象的引用引入的。变量的名称(如果有)表示引用或对象。
从这里开始,根据 [basic]/4 和 [basic]/5,extern
变量声明(必须具有名称)不是特例,它的名称确实表示一个实体:
/4名称是标识符、运算符函数 ID的使用,文本运算符 ID、转换函数ID 或表示实体或标签的模板 ID([stmt.goto]、[stmt.label])。
/5表示实体的每个名称都由声明引入。每个表示标签的名称都由 goto 引入 语句或标记语句。
因为它声明了一个变量,其名称表示对象(即实体)。
但是,[intro.object]/1 说:
[...]对象是通过定义 [...]创建的。
[...]对象具有类型[...]。
但是使用extern
说明符声明的变量的类型不一定与在其他地方定义的对象的类型相同(在重新声明中,变量的非extern
定义):
#include <type_traits>
extern int arr[]; // extern declaration
using A = decltype(arr);
int arr[10]; // definition
using B = decltype(arr);
// All asserts below pass.
static_assert(!std::is_same_v<A, B>, "");
static_assert(std::is_same_v<A, int[]>, "");
static_assert(!std::is_same_v<A, int[10]>, "");
static_assert(std::is_same_v<B, int[10]>, "");
static_assert(!std::is_same_v<B, int[]>, "");
如果extern
变量声明(并且仅arr
)引入的名称表示实体,特别是对象的实体,则可以说意味着该实体根据上下文/范围具有不同的类型,这可以说违反了[intro.object]/1关于对象(实体)具有"[单一]类型">的规定。
不幸的是,"变量"和"对象"之间的混淆存在于整个标准中(考虑到[basic.stc.auto]完全忽略了递归的可能性),包括其中哪一个是"实体"。 但是,[basic.types]/6 非常直接地解决了这个问题:
数组对象的声明类型可能是未知边界的数组,因此在转换单元中的某一点不完整,稍后完成;这两个点的数组类型("未知
T
边界数组"和"N
T
数组")是不同的类型。
对冲"声明类型"在其他地方使用,如[dcl.type.auto.deduct]来谈论"写"某物的类型,即使"真实事物"具有不同的(完成或推断)类型。 这是标准中另一种不幸的描述风格的一个例子:指定应用于程序的转换,而不明确它们的应用顺序或每个阶段适用的规则。 通常很清楚必须应用什么上下文才能使规则有意义:decltype
不考虑指定数组大小的后续声明,也不会生成auto
类型。