如果表达式的求值需要对引用求值,为啥它不是“核心常量表达式”?
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;
| ^~~~~
但如果我们删除&
,它会编译。
另一方面,如果我只是依靠注释,上面写着‘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 = &i == &j
工作得很好。同时,constexpr int * b = &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
。因此初始化不是一个常量表达式,程序格式错误。
【讨论】:
以上是关于如果表达式的求值需要对引用求值,为啥它不是“核心常量表达式”?的主要内容,如果未能解决你的问题,请参考以下文章