下面的代码在GCC,clang和VS2017中编译,return
语句中的表达式a->i
替换为其常量值1。说这是有效的是否正确,因为表达式a->i
中没有使用 odra
?
struct A
{
static const int i = 1;
};
int f()
{
A *a = nullptr;
return a->i;
}
PS:我相信a
在表达式中没有使用a->i
,因为它满足 [basic.def.odr]/4 中的"除非"条件,如下所示:
名称显示为潜在计算值的变量
x
表达式ex
由ex
使用 ODR,除非应用 左值到重值的转换 (7.1( 到x
产生常量表达式 (8.6( 不援引任何非平凡的内容 函数,如果x
是一个对象,则ex
是表达式e
的潜在结果集合的一个元素,其中 左值到重值转换 (7.1( 应用于e
,或者e
丢弃值表达式 (8.2(。
特别是,表达式ex == a
是表达式e == a->i
的潜在结果集合的一个元素,根据 [basic.def.odr]/2 (2.3(,包含表达式ex
,其中左值到右值的转换应用于e
。
a
被 ODR 使用,因为你失败了"除非"的第一部分:
将左值到右值转换 (7.1( 应用于
x
会产生一个常量表达式 (8.6(,该表达式不调用任何非平凡函数
将左值到右值的转换应用于a
不会生成常量表达式。
其余的是核心问题315和232。
您的分析以另外两种方式中断:
- "对象表达式"是使用类成员访问的
.
形式定义的,因此在应用 [basic.def.odr]/2.3 之前,您需要将a->i
重写为点形式,即(*a).i
。a
不是该表达式的潜在结果集的成员。 - 该项目符号本身是有缺陷的,因为它是在考虑非静态数据成员的情况下编写的。对于静态数据成员,潜在结果集实际上应该是命名的静态数据成员 - 请参阅核心问题 2353,因此
a
加倍不是该表达式的潜在结果集的成员。
[expr.const]/2.7:
表达式
e
是核心常量表达式,除非e
的评估,遵循抽象机器的规则,将 计算以下表达式之一:
- [...]
- 左值到右值的转换,除非它应用于
- 整数或枚举类型的非易失性 gl值,引用具有前面的完整非易失性 const 对象 初始化,使用常量表达式初始化,或
- 引用字符串文本的子对象的非易失性 glvalue ,或
一种非易失性- gl值,它引用用
constexpr
定义的非易失性对象,或者引用 这样的对象,或- 文字类型的非易失性 gl值,指的是其生命周期始于
e
评估的非易失性对象;- [...]
i
是该类的static
成员...由于您可以通过使用实例的常规方法来访问类的static
成员,因此它们不特定于任何实例,因此您无需取消引用nullptr
指针(就像使用sizeof
运算符一样(。 您也可以使用简单的
return A::i;
语句,因为您无需创建实例即可访问它。 事实上,const
,编译器允许将其作为常量值进行管理,因此只有在您需要使用它的地址(通过&
运算符(的情况下,编译器才能绕过将其分配给只读内存。
以下示例将对此进行探测:
#include <iostream>
struct A {
static const int i = 1;
};
int main()
{
std::cout << ((A*)0)->i << std::endl;
std::cout << A::i << std::endl;
}
将打印
$ a.out
1
1
$ _