【问题标题】:The question about a glvalue constant expression used in a context that requires a constant expression关于在需要常量表达式的上下文中使用的泛左值常量表达式的问题
【发布时间】:2020-05-21 02:25:34
【问题描述】:
#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;
}

考虑上面的代码,我只是不明白为什么代码格式正确。我的观点是名称v在评估表达式func2时用作glvalue,因为func2的参数是引用类型,v 需要绑定到 id-expression id。所以我们看一下 glvalue 常量表达式的要求,这里是关于它的引用。

常量表达式要么是泛指值核心常量表达式,它引用作为常量表达式(如下定义)的允许结果的实体,要么是值满足以下约束的纯右值核心常量表达式。

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

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

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

在我的程序部分,const int v = 0;没有静态存储时长,它只有自动存储时长。所以在评估表达式func2(v)以确定它是否是一个常量表达式时,首先,v 必须是一个泛左值核心常量表达式,它指的是一个实体,它是一个常量表达式的允许结果,因此,为什么这里的程序格式正确?如果我丢失了任何重要的引用,请纠正我。

【问题讨论】:

    标签: c++ language-lawyer


    【解决方案1】:

    我们忽略纯右值的情况,因为这里 v 被用作泛左值

    是吗?这是来自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&amp;、const_cast 和const,并为其分配运行时值。但是,考虑到 integral constant expression 和 what are the requirements of a const object 是什么,这是完全合理的:

    整型常量表达式是整型或无范围的表达式 枚举类型隐式转换为纯右值,其中 转换后的表达式是核心常量表达式。如果一个表达式 类类型用于整数常量表达式 预期,表达式在上下文中隐式转换为 整数或无作用域枚举类型。

    变量b 确实看起来像是可以隐式转换为纯右值常量表达式的东西,因为在这种情况下它基本上用作文字42 的别名,而整数文字根据定义是纯右值。

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

    const object - 类型为 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: nonstandard extension used : zero-sized array in struct/union 警告。关闭它们后,程序将无法编译。当我从结构中删除 array 部分时,它再次开始编译。

    【讨论】:

    • 在你的例子中,对于constexpr const int&amp; ra = a; ,因为a是一个静态存储时长的对象,对于constexpr int ia = a;,因为在a上进行了l-to-v转换,a是不是由常量表达式初始化的,所以它的格式不正确。
    • *"const int b = 42 在这里很奇怪,因为从技术上讲,您可以将b 绑定到const int&amp;、const_cast 和const 并为其分配运行时值”。修改 const 对象是未定义的行为,所以不,你不能这样做。
    • @Jarod42 未定义的行为意味着无法预测某事的结果,而不是无法做某事。
    • 你可以做一些不允许的事情,所以有 UB 或格式不正确的程序,但重点是什么?我看不出你回答的重点。
    【解决方案2】:

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

    一个完整的表达式是:

    • [...]
    • init-declarator 或 mem-initializer,包括初始化器的组成表达式

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

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

    • 它是用常量表达式初始化的,或者,
    • 它的生命周期始于对 e 的评估;

    对于id-expressionid,它前面的初始化就是对应的参数,因为这个规则:

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

    所以,第一个条件为真。“它是用常量表达式初始化”为假,条件“它的生命周期始于对 e 的求值”为真。总之,表达式 func2(v) 确实是一个常量表达

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多