我昨天发了一个帖子,但是我觉得它不清楚,我得到的回复根本没有解决我的困惑。所以,我会试着让这个例子更简单。
为什么允许这样做:
constexpr int incr(int k1)
{
return k1 + 5;
}
constexpr int foo(int k) // runs in compile time
{
return incr(k);
}
int main() {
constexpr int x = 5;
constexpr int y = foo(4);
}
但这个“非常”相似的功能不是吗?
constexpr int incr(int k1)
{
return k1 + 5;
}
constexpr int foo(int k) // runs in compile time
{
constexpr int x = incr(k); // k: not a constant expression
return x;
}
int main() {
constexpr int x = 5;
constexpr int y = foo(4);
}
在常量求值的上下文中,这两个函数究竟有什么不同?对我来说,它们看起来几乎完全相同,除了一个立即返回,而另一个首先计算值并将其赋值给constexpr变量。
如果可以的话,也请参考标准,以便我进一步阅读!
2条答案
按热度按时间lrpiutwd1#
关于
constexpr
变量的规则是[dcl.constexpr]/6,[...]在任何
constexpr
变量声明中,初始化的完整表达式应该是常量表达式([expr.const])。作为对象的constexpr
变量,以及constexpr
引用绑定到的任何临时变量,应该具有常量析构。每个常量表达式都必须是一个核心常量表达式,还有一些额外的限制,在这里不相关,所以我不讨论它们([expr.const]/13)。如果
constexpr
变量的初始化不是一个核心常量表达式,那么这个程序就是病态的。第二个例子违反的特定规则是[expr.const]/5.9。
表达式 E 是一个核心常量表达式,除非 E 的求值,遵循抽象机器的规则([intro.execution]),将求值以下之一:
在宣言中
完整表达式“从
incr(k)
初始化int
变量”不能作为核心常量表达式,因为当它被求值时,它需要对k
执行左值到右值转换,以便获得初始化k1
所用的值。变量k
在常量表达式中不可用,它的生存期也不是在全表达式“从incr(k)
初始化int
变量”内开始的。在第一个例子中,你的编译器接受,
incr(k)
* 也 * 在编译时被求值,但它不需要是核心常量表达式,所以没有问题。一开始会很混乱,但我们需要记住,不是核心常量表达式的东西可以作为“更大”求值的一部分进行求值,“更大”求值是 * 核心常量表达式。在这种情况下,它是封闭的constexpr
变量初始化(y
的),它是核心常量表达式,因为它创建了函数参数k
(用值4初始化它)然后 * 然后 * 从中读取。换句话说,如果 E 是“用foo(4)
初始化y
”,则k
的生存期在 E 内开始,因此k
的读取可以在不阻止 E 成为核心常量表达式的情况下发生。pvabu6sv2#
constexpr
修饰符应用于函数时,仅给出函数在编译时运行的可能性。(此外,在C++23草案中,constexpr
函数不再受到以前的限制,因此您最好将其视为作者为支持constexpr
语义而给出的契约“promise”)。修饰符并没有取消这一事实,同样的函数也可以在运行时运行:因此,不能保证传递给
constexpr
函数的参数在编译时计算。另一方面,
constexpr
修饰符,当应用于 variables 时,只能**在编译时计算,编译器合理地阻止您使用函数的(可能)运行时参数来计算编译时变量。在不断评估的背景下,这两种功能究竟有何不同?
我认为这里的主要误解是,如果
constexpr
函数是在编译时计算的,那么该函数的所有部分都是在编译时计算的,无论内部的变量是否有constexpr
修饰符。因此,该函数的正确等效形式是:将是: