从另一个Constexpr中调用Constexpr



我知道,这些问题很早就被问到了(例如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);

我们有什么?

  1. 我们用文字常量调用consteval函数。好的
  2. 在consteval函数中,我们在第2行使用非constexpr局部参数对该函数进行递归调用。虽然我们用非constexpr值调用consteval函数,但一切都很好。好吧,我们可以建议,编译器知道,基调用已经作为右consteval调用factorial(5)完成,并且整个最终表达式(带有factorial的所有内部代码)应该被解释为consteval。对或者,为什么?因为
  3. 在第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)不需要是常量表达式。

最新更新