关于在需要常量表达式的上下文中使用的glvalue常量表达式的问题

The question about a glvalue constant expression used in a context that requires a constant expression

本文关键字:常量 表达式 glvalue 问题 上下文      更新时间:2023-10-16
#include <iostream>
constexpr int func2(int const& id){
return id;
}
template<int v>
struct Test{
};
int main(){
const int v = 0;
Test<func2(v)> c;
}

考虑上面的代码,我只是不明白为什么代码格式很好。我的观点是,当求值表达式func2时,名称v被用作glvalue,因为func2的参数是引用类型的,所以v需要绑定到id表达式id。所以我们来看看glvalue常量表达式的要求,这里有关于这一点的引号。

常量表达式是指作为常量表达式(如下定义)的允许结果的实体的glvalue核心常量表达式,或者是其值满足以下约束的prvalue核心常量表达。

我们忽略prvalue的情况,因为这里v被用作glvalue。

一个实体是常量表达式的允许结果,它是:

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

在我的程序部分中,const int v = 0;没有静态存储持续时间,它只有自动存储持续时间。因此,当对表达式func2(v)求值以确定它是否是常数表达式时,首先,v必须是glvalue核心常数表达式,它指的是作为常数表达式的允许结果的实体,因此,为什么程序在这里格式良好?如果我丢失了任何重要报价,请更正。

我们忽略prvalue的情况,因为这里v被用作glvalue

是吗?这是cppreference中的一个例子:

void test() {
static const int a = std::random_device{}();
constexpr const int& ra = a; // OK: a is a glvalue constant expression
constexpr int ia = a; // Error: a is not a prvalue constant expression
const int b = 42;
constexpr const int& rb = b; // Error: b is not a glvalue constant expression
constexpr int ib = b; // OK: b is a prvalue constant expression
}

是的,const int b = 42在这里相当奇怪,因为从技术上讲,你可以将b绑定到const int&const_castconst,并为其分配一个运行时值。然而,考虑到什么是积分常数表达式,const对象的要求是什么,这是完全合理的:

积分常数表达式是积分或未缩放的表达式枚举类型隐式转换为prvalue,其中转换表达式是一个核心常量表达式。如果表达式在整型常量表达式为应为,表达式在上下文中隐式转换为整型或无范围枚举类型。

变量b看起来确实像是可以隐式转换为prvalue常量表达式的东西,因为它在该上下文中基本上充当文本42的别名,而整数文本根据定义是prvalue。

现在对于有问题的部分-这个:

const对象-类型为const限定的对象,或const对象的不可变子对象。这样的对象不能修改:尝试直接这样做是一个编译时错误,并且尝试间接地(例如,通过修改const对象通过指向非常量类型的引用或指针)导致未定义行为

和:

核心常量表达式是任何求值为不评估以下任何一项:

一种表达式,其评估可导致任何形式的核心语言未定义的行为(包括有符号整数溢出、除以零、数组边界外的指针算术等)。是否标准检测到库未定义的行为是未指定的。

意味着一旦你开始用b做有趣的事情,你就可以期待任何事情的发生。例如,这就是我在最新的MSVC中尝试对您的代码所做的,所有标准一致性选项都打开了:

#include <iostream>
#include <random>
constexpr int func2(int const& id) {
return id;
}
template<int v>
struct Test {
long array[v];
};
int main() {
const int v = 0;
const int& ref = v;
const_cast<int&>(ref) = std::random_device()() % std::numeric_limits<int>::max();

Test<func2(v)> c;
return 0;
}

打开语言扩展后,我得到了一个C4200:使用了非标准扩展:结构/联合警告中的零大小数组。关闭后,程序将无法编译。当我从结构中删除array部分时,它又开始编译了。

我试图回答这个问题。为什么func2(v)是一个常量表达式,因为对于表达式func2(v),当评估这个后缀表达式时,不要求v必须是"将评估以下表达式之一"列表中的glvalue常量表达式:",Even,这些规则并不要求潜在核心常量表达式中的一个表达式是glvalue常量表达式,只要求该表达式不违反列出的要求。让我们继续,当初始化参数时,这里有另一条规则:

完整的表达式是:

  • […]
  • init声明符或mem初始化器,包括初始化器的组成表达式

所以,当对这个完整表达式求值时,它只会不违反这些列出的条件,那么func2(v)就会被求值为一个常量表达式,所以让我们看看这些规则:

一个id表达式,它引用引用类型的变量或数据成员,除非该引用具有先前的初始化和

  • 它是用常量表达式初始化的,或者
  • 其寿命始于e的评价

对于id表达式id,其前面的初始化是相应的参数,因为这个规则:

调用函数时,每个参数([dcl.fct])都应初始化([dcl.init]、[class.copy]、[cclass.ctor])及其相应的参数。

因此,第一个条件为true。"它是用常量表达式初始化的"为假,条件"它的生存期在e的求值范围内开始"为真。结论func2(v)表达确实是的恒定表达