31

N4527 5.20[expr.const]p5

一个常量表达式要么是一个左值核心常量表达式,其值是指一个实体,该实体是一个常量表达式(如下定义)的允许结果,要么是一个纯右值核心常量表达式,其值是一个对象,对于该对象及其子对象:

— 每个引用类型的非静态数据成员都引用一个实体,该实体是一个常量表达式的允许结果,并且

— 如果对象或子对象是指针类型,则它包含具有静态存储持续时间的对象的地址、此类对象末尾的地址 (5.7)、函数的地址或空指针值。

如果实体是具有静态存储持续时间的对象,该对象不是临时对象或者是其值满足上述约束的临时对象,或者它是一个函数,则该实体是常量表达式的允许结果。

void foo(){
    int a = 1;
    int b[a || 1]{};//ok in gcc 5.1.0, error in clang 3.8.0
    static_assert(a || 1,"");//ok in gcc 5.1.0, error in clang 3.8.0
    switch(1){
        case a || 1://ok in gcc 5.1.0, error in clang 3.8.0
            ;
        }
}

a || 1常量表达式吗?


N4527 5.20[expr.const]p2

条件表达式 e 是核心常量表达式,除非按照抽象机 (1.9) 的规则对 e 的求值将求值以下表达式之一:

(2.7) — 左值到右值的转换 (4.1),除非它应用于

(2.7.1) — 一个整数或枚举类型的非易失性左值,它指的是一个完整的非易失性 const 对象,该对象具有前面的初始化,用常量表达式初始化,或

(2.7.2) — 引用字符串字面量 (2.13.5) 的子对象的非易失性泛左值,或

(2.7.3) — 一个非易失性泛左值,它指代一个用 constexpr 定义的非易失性对象,或者指代这种对象的一个​​非可变子对象,或

(2.7.4) — 文字类型的非易失性左值,指的是一个非易失性对象,其生命周期开始于对 e 的评估;

a || 1核心常量表达式吗?

4

3 回答 3

24

a不是常量表达式(参见下面的标准引用),因此:

a || 1 

也不是一个常量表达式,尽管我们知道表达式必须计算为真,但标准要求在这里从左到右进行计算,我没有看到允许编译器跳过左值到右值转换的例外情况a

但:

const int a = 1;

可以在常量表达式中使用,因为它属于5.20p2强调我的)的例外:

左值到右值的转换(4.1),除非它应用于

  • 一个整数或枚举类型的非易失性左值,它引用一个完整的非易失性 const 对象,该对象具有前面的初始化,用常量表达式初始化,或
  • 引用字符串文字 (2.13.5) 的子对象的非易失性泛左值,或
  • 一个非易失性泛左值,它指的是一个用 constexpr 定义的非易失性对象,或者指代这种对象的一个​​非可变子对象,或者
  • 文字类型的非易失性左值,它引用一个非易失性对象,该对象的生命周期在 e 的评估中开始

这条规则也是为什么原始 case 不是常量表达式的原因,因为没有任何例外适用。

也许gcc允许这样做:

int b[a || 1]{};

作为一个可变长度数组作为扩展,虽然它应该使用-pedantic. 尽管这不能解释 static_assert 的情况,但它们可以对其进行常量折叠,但我认为 as-if 规则不会允许将其视为常量表达式。

更新,可能的 gcc 扩展

从这个错误报告中,逻辑运算符的 RHS 可能会导致 LHS 在常量表达式中未求值,这看起来像是一个可能的 gcc 扩展:

尽管在常量表达式中使用了非常量对象,但编译过程不会发生意外:

int i;
static_assert( i || true, "" );
static_assert( ! ( i && false ), "" );

似乎假设 || 和 && 是可交换的,但短路只在一个方向上起作用。

最后的评论说:

我认为这是一个有目的的语言扩展,可以使用开关来禁用。如果 static_assert 总是严格的就好了。

这似乎是一个不符合标准的扩展,当使用-pedantic类似的标志时应该触发警告,以在将非 constexpr 标准库函数视为 constexpr 是否符合标准的编译器扩展?.

C++11/C++14 引用

Section5.205.19C++14 和 C++11 中的部分,C++14 标准草案的相关引用是:

左值到右值的转换(4.1),除非它应用于

  • 整数或枚举类型的非易失性左值,它引用具有先前初始化的非易失性 const 对象,用常量表达式初始化 [注意:字符串文字 (2.14.5) 对应于此类对象的数组。——尾注],或

  • 一个非易失性泛左值,它指的是一个用 constexpr 定义的非易失性对象,或者指代这种对象的一个​​非可变子对象,或者

  • 文字类型的非易失性左值,它引用一个非易失性对象,其生命周期在 e 的评估中开始;

对于 C++11 标准草案是:

左值到右值的转换(4.1),除非它应用于

  • 一个整数或枚举类型的左值,它引用一个非易失 const 对象,该对象具有前面的初始化,用常量表达式初始化,或者

  • 一个字面量类型的左值,它引用一个用 constexpr 定义的非易失性对象,或者引用这样一个对象的子对象,或者

  • 一个字面量类型的左值,它引用一个生命周期尚未结束的非易失性临时对象,用一个常量表达式初始化;

于 2015-07-20T21:06:15.757 回答
2

重复你的报价:

(2.7) — 左值到右值的转换 (4.1),除非它应用于

(2.7.1) — 一个整数或枚举类型的非易失性左值,它引用一个完整的非易失性常量对象,该对象具有前面的初始化,用常量表达式初始化,或

a涉及左值到右值的转换。由于a不是 const 对象,这意味着a不是核心常量表达式;因此a || 1也不是一个。

但是,如果您的代码是:

const int a = 1;

thena || 1将是一个核心常量表达式。

于 2015-07-20T21:59:10.750 回答
1

如果编译器检查整个赋值链,那么它可以确定“a || 1”是一个常量表达式。但是,由于 a 是一个变量,除非编译器检查 a 没有被赋值,否则它无法知道“a || 1”是一个常量表达式。

于 2015-07-20T21:05:41.603 回答