问题 说编译器可以将下面的表达式'a-> i`替换为值1是正确的,因为......?


下面的代码编译在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


897
2018-05-19 18:31


起源

这是一个很好的问题,精心设计和编写(所以+1)。但 为什么 有人会这样做吗? - Some programmer dude
我无法相信这个编译并按顺序出现。期待更聪明的人的解释。 - DeiDei
@Someprogrammerdude我得到了这个例子 这个讨论 在C ++ std-discussion中,但我认为当他说变量时OP是不正确的 s 在他的例子中使用的是odr,相当于上面给出的那个。 - Ayrosa
难道你不是忘了它说“产生一个不变的表达[和] [你写的]”? a 不是一个恒定的表达。 - Rakete1111


答案:


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;
  • [...]

11
2018-05-19 18:44



@Ayrosa那不做 a 一个恒定的表达。 int b = 1; 是 b 这是一个常数表达因为 1 是什么? - Rakete1111
@Ayrosa一个l-t-r转发 a 不屈服 nullptr,它产生一个具有该值的右值 nullptr。该右值不是一个常数表达式。 - Rakete1111
@Ayrosa你问的是有什么用的 a。有问题的报价意味着你问是否应用l-t-r a 产生一个常数表达式。它没有。 - T.C.
你有一个 非常 奇怪的“收益率”定义,以某种方式设法跳过了应用l-to-r转换的行为这一事实 a 不能以常量表达式出现。 (此外,应用l-to-r的结果 a 不是 nullptr;它是类型的空指针值 A*。) - T.C.
@Alexander这非常有意义,你可以在今天的真实编译器中看到它。特定 struct S { static const int i = 0; };,事实 int main() { const int *pi = &S::i; return *pi; } 不起作用(没有优化)表明这不提供定义 S::i但是 int main() { return S::i; } 仍然有效,并保证工作。为此,编译器必须有效地替换 S::i 按字面意思 0,避免需要定义。它只能用于常量表达式。对于非常量表达式,它需要定义才能实际加载值。


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
$ _

-2
2018-05-23 07:16



感谢@RobertAndrzejuk,格式化....我发布之前忘了这么做。不知道格式是否是downvote的原因,但是对downvote的原因的评论将不胜感激。 - Luis Colorado
也许有人不喜欢这个答案...... - Robert Andrzejuk
downvote应该被用来作为一种改善反应的方式,而不是通过低估其他人来竞争声誉......反应是有效的,并通过一个有效的,完整的,可验证的例子,原始问题所涉及的事情来探测回应。甚至做。让我们给下注者一个解释自己的机会。 - Luis Colorado
这个答案的明显缺陷是它没有回答这个问题。这篇文章有标记 语言律师。这是关于语言规范本身的复杂性。因此,甚至没有引用该标准的答案也没有用。投票反映了(正如downvote按钮上的帮助文本指定的那样)。这就是为什么我也反对“竞争代表”的毫无根据的假设。你的时间会更好地用这个问题要求严格审查自己的帖子,而不是假装其他人犯规。我们很清楚,这是我的投票。 - StoryTeller
@StoryTeller感谢您的解释。现在这个downvote有一些优点。 downvote本身是非建设性的 - 就像接受沉默的治疗,因为“你知道你做了什么!现在解决它!”但实际上人们不知道。 - Robert Andrzejuk