【问题标题】:int a=1, is a || 1 a constant expression?int a=1, 是一个 || 1 常量表达式?
【发布时间】:2015-10-10 03:40:26
【问题描述】:

N4527 5.20[expr.const]p5

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

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

—如果对象或子对象是指针类型,则它包含具有静态存储持续时间的对象的地址、经过此类对象末尾的地址 (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 是核心常量表达式,除非 e 的求值遵循 抽象机器(1.9),将评估以下表达式之一:

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

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

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

(2.7.3) — 一个非易失性泛左值,它引用一个用 constexpr 定义的非易失性对象,或者引用 到此类对象的非可变子对象,或

(2.7.4) — 文字类型的非易失性左值,它引用一个生命周期开始的非易失性对象 在e的评价范围内;

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

【问题讨论】:

  • AFAIK GCC 具有“可变长度数组”功能 (gcc.gnu.org/onlinedocs/gcc/Variable-Length.html),它可能负责 int b[a || 1] 的工作。不过,我知道的还不够多,无法正确回答您的问题。
  • 引用N4527 有什么特别的理由吗?这种特殊情况对于 C++11 和 C++14 应该是相同的,尽管引号会略有不同。
  • @ShafikYaghmour 因为它是最近的,我从 github 下载。另一个关于 5.20[expr.const] 的问题,见stackoverflow.com/questions/31527913/…
  • 这个问题是 C++1z 是一个移动的目标,所以措辞可能会发生变化。而在 C++11/14 中,它们已经完成,据我所知,更改只能通过缺陷报告应用,虽然并不完美,但它们在某种程度上是自我记录的。 C++11/14 也是人们在生产中使用的(可能主要是 C++11),因此更相关,恕我直言。对于这个问题,据我所知,其他问题的答案是相同的,尽管引号将针对特定标准。

标签: c++ language-lawyer constant-expression c++17


【解决方案1】:

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

a || 1 

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

但是:

const int a = 1;

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

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

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

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

也许gcc 允许这样做:

int b[a || 1]{};

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

更新,可能的 gcc 扩展

从这个错误报告RHS of logical operators may render LHS unevaluated in constant-expression 这看起来像是一个可能的 gcc 扩展:

尽管在 一个常量表达式:

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

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

最后的评论说:

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

这似乎是一个不符合标准的扩展,当使用类似于在Is it a conforming compiler extension to treat non-constexpr standard library functions as constexpr? 中发布的-pedantic 标志时会触发警告。

C++11/C++14 引用

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

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

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

  • 一个非易失性泛左值,它引用一个用 constexpr 定义的非易失性对象,或引用 到此类对象的非可变子对象,或

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

C++11 标准草案是:

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

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

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

  • 一个文本类型的左值,它引用一个非易失性临时对象,其生命周期没有 结束,用常量表达式初始化;

【讨论】:

  • 这不仅仅是在数组中,static_assert(a || 1,""); 在 gcc 中也可以,所以 gcc 认为 a || 1 是一个常量表达式,但 clang 不是
  • 是的,所以在static_assert期间使用它应该是一个错误,也许它是不断折叠表达式,因为它可以看到它必须是真的。
  • 我已经更新了我的问题。 a不是常量表达式,a || 1呢?
  • 如果a 不是常量表达式,任何使用它的计算也不会(即a || 1)。一个表达式是恒定的,在某种程度上取决于它的所有部分都是恒定的。所以答案是否定的。
【解决方案2】:

重复你的报价:

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

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

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

但是,如果您的代码是:

const int a = 1;

那么a || 1 将是一个核心常量表达式。

【讨论】:

  • 曾经有文本表明实现可以将其他表达式视为超出标准规定的最小值的常量表达式,但我再也看不到了
  • 在 SFNAIE 之前的日子里,让编译器将额外的东西视为常量表达式意味着它会接受其他编译器会拒绝的有效代码,并且可能会生成比编译器更有效的代码不识别那些常量表达式,但不会改变其他编译器可以接受的代码的含义。将 SFNAIE 添加到该语言中,可以表达许多以前无法表达的东西,但也使得某些东西不是恒定的语义意义重大。
【解决方案3】:

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

【讨论】:

  • 如果a 的评估没有副作用,那么||(恕我直言有点无益)将每个操作数转换为0或1这一事实意味着a || 1将是相当于C中的1。当然,SFINAE意味着类似的原理可能不适用于C++。
  • 标准要求||从左到右求值,这意味着这里不允许避免左值到右值的转换。
猜你喜欢
  • 2015-10-10
  • 2021-02-08
  • 1970-01-01
  • 2022-12-17
  • 2019-06-22
  • 1970-01-01
  • 1970-01-01
  • 2014-02-11
  • 2011-09-07
相关资源
最近更新 更多