我知道,这些问题很早就被问到了(例如constexpr函数中的非constexpr调用),但让我们来看下一段代码:
consteval int factorial(int n)
{
return n <= 1 ? 1 : (n * factorial(n - 1));
}
factorial(5);
一切正常。我们保证,factorial(5)
表达式在编译时解析,因为consteval
。正确的如果是这样,我认为这应该意味着调用factorial(5)中的递归factorial(n - 1)
也在编译时解析。然而,我们也知道,在声明int factorial(int n)
中,参数int n
只是一个变量,而不是constexpr
。如果我们尝试这样做,这会影响
consteval int factorial(int n)
{
// 1
constexpr auto res = factorial(n - 1); // error: ‘n’ is not a constant expression
// 2
return n <= 1 ? 1 : (n * factorial(n - 1)); // hhhhmmmmmm...but all is ok..
}
factorial(5);
我们有什么?
- 我们用文字常量调用consteval函数。好的
- 在consteval函数中,我们在第2行使用非constexpr局部参数对该函数进行递归调用。虽然我们用非constexpr值调用consteval函数,但一切都很好。好吧,我们可以建议,编译器知道,基调用已经作为右consteval调用
factorial(5)
完成,并且整个最终表达式(带有factorial
的所有内部代码)应该被解释为consteval。对或者,为什么?因为 - 在第1行,我们显式地调用具有非constexpr值的constexpr。我们得到了一个错误
我的问题是:为什么factorial(5)
编译器的显式consteval调用会使阶乘的显式和隐式constexpr递归调用产生差异?是bug还是特性?
让我们回顾一下什么是常量表达式。核心常量表达式是一个在求值时不会导致一长串";坏的";行为。常数表达式是核心常数表达式,其结果是"0";允许的";根据其他一些规则(此处不重要)。特别要注意的是,这些条件在很大程度上是非句法的:常数表达式不是通过定义什么表达式是常数表达式来正向定义的,而是通过定义常数表达式不能做什么来反向定义的。
这个定义的结果是,一个表达式可以是一个常量表达式,即使它需要对许多非常量表达式(甚至是非核心常量表达式)进行求值。在的定义中
consteval int factorial1(int n) {
if(n == 0) return 1;
else { // making this correct since undefined behavior interferes with constant expressions
/*constexpr*/ auto rec = factorial1(n - 1);
return n * rec;
}
}
consteval int factorial2(int n) {
return n == 0 ? 1 : n * factorial2(n - 1);
}
factorial1
中的factorial1(n - 1)
不是常数表达式,因此将constexpr
添加到rec
是错误的。类似地,factorial2
中的n == 0 ? 1 : n * factorial2(n - 1)
是,也不是常数表达式。原因是相同的:这两个表达式都读取对象n
的值(对其执行左值到右值的转换),而CCD_16并没有在表达式中开始生存期。但这很好:constexpr
/consteval
函数的主体根本不被检查为常量表达式constexpr
真正所做的就是将函数的调用列入白名单,以便出现在常量表达式中。同样,表达式可以是常量(如factorial1(5)
),即使您需要在计算过程中计算一个非常量表达式(如CCD21)。(在这种情况下,当评估factorial1(5)
时,作为factorial
的参数的n
对象的生存期确实在被检查的表达式内开始其生存期,因此可以在评估期间读取它。)
表达式将检查为常量表达式的两个地方是CCD_;非受保护";调用CCD_ 26函数。第一个解释了为什么在factorial1
中将constexpr
添加到rec
是一个错误:您为常量表达式添加了一个额外的检查,而不是在正确的factorial1
函数中完成的,而这个额外的检查(正确地)失败了。这本应该回答你的问题3。
对于你的观点2:是的,有一个特别的";"保护";用于从其它CCD_ 32函数调用的CCD_。通常,对consteval
函数的调用在编写时会被检查为常量表达式。正如我们所讨论的,对于上述定义中的调用factorial1(n - 1)
和factorial2(n - 1)
,此检查将失败。该语言中内置了一个特殊情况来保存它们:在立即函数上下文中对consteval
函数的调用(基本上,其立即封闭函数也是consteval
)不需要是常量表达式。