如果表达式的求值需要对引用求值,为啥它不是“核心常量表达式”?

Posted

技术标签:

【中文标题】如果表达式的求值需要对引用求值,为啥它不是“核心常量表达式”?【英文标题】:Why isn't an expression a "core constant expression" if its evaluation requires the evaluation of a reference?如果表达式的求值需要对引用求值,为什么它不是“核心常量表达式”? 【发布时间】:2021-03-22 03:39:44 【问题描述】:

(这是对this问题的跟进。)

所以我想专门问一个问题,以了解我收到的that answer中引用的标准,而我的确切问题在标题中。

老实说,即使在cppreference 上,我也无法理解为什么标准如此规定的原因。

但是,这是一个最小的例子:

#include <array>
int main() 
    auto arr = std::array<int,3>1,2,3;
    constexpr auto size1 = arr.size(); // OK
    auto const& array = arr;
    constexpr auto size2 = array.size(); // does not compile

编译不报错(报错信息与-std=11/14/17/2a相同,因此标签为两个极端)

$ g++ -std=c++17 deleteme.cpp && ./a.out 
deleteme.cpp: In function ‘int main()’:
deleteme.cpp:6:39: error: the value of ‘array’ is not usable in a constant expression
    6 |     constexpr auto size2 = array.size(); // does not compile
      |                                       ^
deleteme.cpp:5:17: note: ‘array’ was not initialized with a constant expression
    5 |     auto const& array = arr;
      |                 ^~~~~

但如果我们删除&amp;,它编译。

另一方面,如果我只是依靠注释,上面写着‘array’ was not initialized with a constant expression,我会假设以下编译

#include <array>
int main() 
    constexpr auto arr = std::array<int,3>1,2,3;
    constexpr auto size1 = arr.size(); // OK
    constexpr auto& array = arr;
    constexpr auto size2 = array.size(); // does not compile

但它没有并且编译器说(消息错误与-std=11/14/17/2a相同)

$ g++ -std=c++17 deleteme.cpp && ./a.out 
deleteme.cpp: In function ‘int main()’:
deleteme.cpp:5:29: error: ‘arr’ is not a constant expression
    5 |     constexpr auto& array = arr;
      |                             ^~~

这基本上意味着arr 不是“常量表达式”,即使它是constexpr,这在我看来至少是一个非常糟糕的措辞。

【问题讨论】:

您能否决定要引用哪个标准文档?我编辑了标签以匹配您报告的测试,但现在您使用 C++11 添加了一个测试 @StoryTeller-UnslanderMonica 描述的行为在所有标准版本 C++11-C++20 中都是相同的。 @ecatmur - 是但不是。在每个已发布的标准中,措辞都越来越精确。对行为的描述几乎不一样。 相关,但不涉及标准行为的动机:***.com/questions/51456712/…***.com/questions/54124899/… 【参考方案1】:

我相信这是因为constant-expression rules 允许我们使用地址运算符,正如this answer 中所暗示的那样。例如this is legal:

void f() 
    int i, j;
    constexpr bool b = &i == &j; // OK, b := false since i and j are distinct objects

允许在常量表达式中使用引用似乎无害:

void g() 
    int i, j;
    int& r = j;
    constexpr bool b = &i == &r; // OK, surely?

但是我们可以使引用的所指对象依赖于非常量变量,然后将该非常量值偷运到常量评估中:

void h(bool a) 
    int i, j;
    int& r = a ? i : j;
    constexpr bool b = &i == &r; // oops, b := a

应该可以将the prohibition on evaluating references 放宽到禁止使用(或使用?)引用地址,但肯定需要一些努力来确保新语言具有预期的效果。

附录:论文P2280R1 "Using unknown references in constant expressions" 旨在打击使您的原始示例格式错误的标准语。它(尚未)详细说明将允许什么,但似乎可能不允许形成或比较指向“未知”对象的指针,这些对象是来自 constexpr 上下文之外的引用的引用,因此g()会一直失效,更别提h()了。

【讨论】:

哦,我喜欢例子!但是,我觉得这种观察取决于(可验证的)事实,即 变量地址constexpr-ish(我想有一个合适的词/形容词,但我不知道),因为它们不能改变;例如,constexpr bool b = &amp;i == &amp;j 工作得很好。同时,constexpr int * b = &amp;i; 无法编译。所以这并不是我想的那样地址是constexpr-ish。 @Enlico 基本上,您可以在常量表达式中使用自动变量的地址,只要这些地址不“转义” - 也就是说,成为eel.is/c++draft/expr.const#11.2 所描述的表达式的结果 也就是说,我可以将int i, j; 移到上述函数之外(进入文件范围)或使它们成为static,它们的地址将是常量表达式的允许结果。 非常感谢。这说明了很多!【参考方案2】:

在变量声明中使用constexpr说明符要求初始化的完整表达式为常量表达式,其规则如下:

对象声明中使用的 constexpr 说明符将对象声明为 const。这样的对象应具有文字类型并应被初始化。在任何 constexpr 变量声明中,初始化的完整表达式应为常量表达式

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

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

核心常量表达式应满足以下规则:

表达式 e 是核心常量表达式,除非根据抽象机的规则对 e 的求值将求值以下表达式之一:

[...] 一个id-expression,它引用一个引用类型的变量或数据成员,除非该引用具有前面的初始化并且
它是用常量表达式或初始化的。 它的生命周期始于对 e 的评估;

在您的示例中,id-expression array 既不会被常量表达式初始化,因为arr 没有静态存储持续时间,它的生命周期也不会在初始化的评估中开始size2 。因此初始化不是一个常量表达式,程序格式错误。

【讨论】:

以上是关于如果表达式的求值需要对引用求值,为啥它不是“核心常量表达式”?的主要内容,如果未能解决你的问题,请参考以下文章

typedef声明变量也是一种求值过程

前缀,中缀,后缀表达式求值

Stack实现表达式的求值

栈的应用 -- 无括号表达式的求值

Dijkstra的双栈算术表达式的求值算法

“从右到左的运算符关联性”是不是与 javaScript 中赋值运算符中的求值顺序相同