关于C23标准中未指定声明(underspecified declarations)作用域规则的三个技术疑问
C23 §6.2.1(7) 中新增了关于未指定声明的作用域规则,其中加粗部分为新增内容:
Structure, union, and enumeration tags have scope that begins just after the appearance of the tag in a type specifier that declares the tag. Each enumeration constant has scope that begins just after the appearance of its defining enumerator in an enumerator list. An ordinary identifier that has an underspecified definition has scope that starts when the definition is completed; if the same ordinary identifier declares another entity with a scope that encloses the current block, that declaration is hidden as soon as the inner declarator is completed.[footnote 19] Any other identifier has scope that begins just after the completion of its declarator.
脚注19的内容如下:
That means that the outer declaration is not visible for the initialiser.
根据C23 §6.7.1(12)的定义,未指定声明(underspecified declaration) 是指不包含任何类型说明符,或者使用constexpr声明的声明。
接下来是我对这些规则的三个疑问,以及自己的初步思考:
问题1:为什么未指定声明的作用域要在定义完成后才开始,而非像普通声明那样在声明符完成后开始?
对于非未指定的普通声明,标识符的作用域在声明符完成后就开始了,但未指定声明的作用域却要等到定义完成后才启动。我自己梳理了一下可能的原因:
- 类型依赖循环问题:如果未指定声明没有类型说明符,它的类型需要从初始化表达式推导。比如写
auto foo = foo * 2;,这里foo的类型依赖初始化表达式,而初始化表达式又依赖foo的类型,形成循环,根本无法推导类型。让作用域延后到定义完成,可以避免这种在初始化阶段引用自身的情况。 - 值依赖循环问题:我猜测这和
constexpr声明有关。普通声明比如int bar = bar * 2;是运行时初始化,此时变量已经分配内存,没问题;但constexpr int baz = baz * 2;需要编译时求值,而编译时baz还不存在,根本无法计算初始化值。延后作用域就能阻止这种编译时的自引用初始化。
但我不确定自己的理解是否准确,想确认这个设计的核心原因。
问题2:为什么未指定声明的声明符完成后就会隐藏外部声明?
按照规则,未指定声明的声明符一完成,就会隐藏外部的同名声明——但此时它自己的作用域还没开始(要到定义完成后才生效)。而普通声明是作用域开始(声明符完成)的同时才隐藏外部声明。为什么未指定声明要采用这种不同的逻辑?
问题3:为什么constexpr声明会被归为“未指定声明”?
根据定义,未指定声明分两类:无类型说明符的,和带constexpr的。但带constexpr的声明往往是有明确类型说明符的,比如constexpr int x = 0;,这样的声明哪里“未指定”?
我自己的猜测是:这可能是语法层面的归类,因为constexpr声明和无类型说明符的声明有相似的语法需求,就像typedef本质不是存储类说明符,但为了语法方便被归为一类。但我想知道这个归类的真正原因。
备注:内容来源于stack exchange,提问作者user51462

