规则是,从[表达式常量]/13 https://timsong-cpp.github.io/cppwp/n4861/expr.const#13:
表达式或转换位于立即函数上下文如果它可能被评估并且其最内部的非块作用域是立即函数的函数参数作用域。表达式或转换是立即调用如果它是立即函数的潜在评估显式或隐式调用并且不在立即函数上下文中。立即调用应是常量表达式。
其中,立即函数只是术语(来自[dcl.constexpr]/2 https://timsong-cpp.github.io/cppwp/n4861/dcl.constexpr#2):
使用以下声明的函数或构造函数consteval
说明符被称为立即函数.
从例子来看:
struct A {
int i;
consteval A() { i = 2; };
consteval void f() { i = 3; }
};
constexpr bool g() {
A a;
a.f();
return true;
}
电话a.f()
是立即调用(我们正在调用立即函数,但我们不在立即函数上下文中,g
is constexpr
not consteval
),所以它一定是一个常量表达式。
它本身必须是一个常量表达式。不是整个调用g()
, just a.f()
.
是吗?不。a.f()
变异a
通过写入a.i
,这违反了[表达式常量]/5.16 https://timsong-cpp.github.io/cppwp/n4861/expr.const#5.16。作为常量表达式的限制之一是不能:
对象的修改([expr.ass]、[expr.post.incr]、[expr.pre.incr]),除非它应用于引用其非易失性对象的文字类型的非易失性左值生命周期开始于评估E
;
我们的对象,a.i
,并没有在该表达式的求值过程中开始其生命周期。因此,a.f()
不是常量表达式,因此所有编译器都可以正确拒绝。
值得注意的是A().f();
会很好,因为现在我们遇到了异常 -A()
在评估该表达式期间开始其生命周期,因此A().i
也做了,因此分配给它就可以了。
你可以认为这意味着A()
对于常量求值器来说是“已知”的,这意味着做A().i = 3;
完全没问题。同时,a
未知 - 所以我们不能做a.i = 3;
因为我们不知道什么a
is.
If g()
were a consteval
函数,则a.f()
将不再是立即调用,因此我们不再要求它本身是一个常量表达式。现在唯一的要求就是g()
是一个常量表达式。
并且,在评估时g()
作为常量表达式,声明A a;
现在在表达式的求值范围内,所以a.f()
并不能阻止g()
从一个常数表达式。
规则差异的产生是因为consteval
函数需要是only在编译时调用,以及constexpr
函数仍然可以在运行时调用。