下面的代码编译在GCC,clang和VS2017以及表达式中 a->i
在里面 return
语句被其常量值1替换。说这是有效的是正确的,因为 a
是 不 在表达中使用的odr a->i
?。
struct A
{
static const int i = 1;
};
int f()
{
A *a = nullptr;
return a->i;
}
PS:我相信 a
是 不 在表达中使用的odr a->i
因为它满足了“除非”的条件 [basic.def.odr / 4, 如下:
一个变量 x
其名称显示为可能已评估的名称
表达 ex
是有用的 ex
除非 应用
左值到右值的转换(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
由于你在“除非”的第一部分失败了,因此使用率很高:
应用左值到右值转换(7.1) x
产生一个不调用任何非平凡函数的常量表达式(8.6)
将左值到右值的转换应用于 a
不会产生常数表达式。
其余的是核心问题 315 和 232。
您的分析还有两种方式:
- “对象表达式”是使用
.
类成员访问的形式,所以你需要重写 a->i
点状,即 (*a).i
,在应用[basic.def.odr] /2.3之前。 a
不是该表达式的潜在结果集的成员。
- 该子弹本身是有缺陷的,因为它是用非静态数据成员编写的。对于静态数据成员,潜在结果集实际上应该是命名的静态数据成员 - 请参阅 核心问题2353所以
a
是双重的不是该表达的潜在结果的成员。
[expr.const] /2.7:
一种表达 e
是一个 核心常数表达式 除非
评估 e
遵循抽象机器的规则,会
评估以下表达式之一:
- [...]
- 除非适用,否则左值转换为右值
- 一个非整数或枚举类型的非易失性glvalue,它引用一个带有前面的完整的非易失性const对象
初始化,用常量表达式初始化,或
- 一个非易失性glvalue,引用字符串文字的子对象,或
- 非易失性glvalue,引用定义的非易失性对象
constexpr
或者指的是一个不可变的子对象
这样的对象,或者
- 一个非易失性的文字类型glvalue,它引用一个非易失性对象,其生命周期始于评估范围内
e
;
- [...]
i
是一个 static
班级成员......因为你可以访问 static
通过对实例使用传统方法的类成员,它们并不特别依赖于任何实例,因此您不需要取消引用 nullptr
指针(就像你使用时一样) sizeof
运营商)。您也可以使用简单的方法访问该字段
return A::i;
声明,因为您不需要创建实例来访问它。确实,存在 const
,编译器允许将其作为常量值进行管理,因此只有在您需要使用它的地址的情况下(通过 &
operator)编译器可以绕过在只读内存中分配它。
以下示例将探测:
#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
$ _