为什么它生病了
根据广义常量表达式 - 第5版,以下是非法的。
constexpr int g(int n) // error: body not just ‘‘return expr’’
{
int r = n;
while (--n > 1) r *= n;
return r;
}
这是因为所有'constexpr'函数都必须是{ return expression; }
{ return expression; }
。 我看不出有什么理由需要这样做。
在我看来,唯一真正需要的是没有外部状态信息被读取/写入,并且传入的参数也是“constexpr”语句。 这意味着任何对具有相同参数的函数的调用都会返回相同的结果,因此可以在编译时“知道”。
我的主要问题在于,它似乎迫使你做出真正迂回的循环形式,并希望编译器优化它,使其对非constexpr调用同样快速。
要为上面的例子写一个有效的constexpr
,你可以这样做:
constexpr int g(int n) // error: body not just ‘‘return expr’’
{
return (n <= 1) ? n : (n * g(n-1));
}
但是这很难理解,你不得不希望当你用违反const-expr
要求的参数调用时,编译器会处理尾递归。
原因在于编译器已经有很多工作要做,而且还没有一个完整的解释器,能够评估任意C ++代码。
如果他们坚持单一的表达方式,他们会限制案件数量的大幅度考虑。 松散地说,它特别简化了很多没有分号的东西。
每一次;
遇到了,这意味着编译器必须处理副作用。 这意味着一些地方的国家在前面的声明中发生了变化,下面的声明将依靠它。 这意味着被评估的代码不再只是一系列简单的操作,每个操作都将前一个操作的输出视为输入,但也需要访问内存,这很难推理。
简而言之,这是:
7 * 2 + 4 * 3
计算简单。 您可以构建如下所示的语法树:
+
/
/
* *
/ /
7 2 4 3
编译器可以简单地遍历这棵树,在每个节点上执行这些基本操作,并且根节点隐含地是表达式的返回值。
如果我们使用多行编写相同的计算,我们可以这样做:
int i0 = 7;
int i1 = 2;
int i2 = 4;
int i3 = 3;
int i4 = i0 * i1;
int i5 = i2 * i3;
int i6 = i4 + i5;
return i6;
这很难解释。 我们需要处理内存读取和写入,并且我们必须处理返回语句。 我们的语法树变得更复杂了。 我们需要处理变量声明。 我们需要处理没有返回值的语句(比如循环或内存写入),但它只是简单地修改某处的内存。 哪个内存? 哪里? 如果它不小心覆盖了一些编译器自己的内存呢? 如果它出现段错误怎么办?
即使没有所有令人讨厌的“假设”,编译器必须解释的代码也变得复杂得多。 语法树现在可能看起来像这样:( LD
和ST
分别是加载和存储操作)
;
/
ST
/
i0 3
;
/
ST
/
i1 4
;
/
ST
/
i2 2
;
/
ST
/
i3 7
;
/
ST
/
i4 *
/
LD LD
| |
i0 i1
;
/
ST
/
i5 *
/
LD LD
| |
i2 i3
;
/
ST
/
i6 +
/
LD LD
| |
i4 i5
LD
|
i6
它不仅看起来更复杂,而且现在也需要国家。 以前,每个子树都可以单独解释。 现在,他们都依赖于程序的其余部分。 其中一个LD叶子操作是没有意义的,除非将它放置在树中,以便先前在相同位置上执行ST
操作。
为了防止这里出现混淆,您应该知道, constexpr
函数/表达式是在编译时进行评估的。 不涉及运行时性能问题。
了解这一点,他们只允许在constexpr
函数中使用单个返回语句的原因是编译器实现者不需要编写虚拟机来计算常量值。
尽管如此,我仍然关注QoI问题。 我想知道编译器的实现者是否会足够聪明地执行memoization?
constexpr fib(int n) { return < 2 ? 1 : fib(n-1) + fib(n-2); }
没有记忆,上面的函数具有O(2n)的复杂性,这当然不是我想要的,即使在编译时也是如此。
据我了解,他们尽可能简单,以免使语言复杂化(事实上,我似乎记得不允许递归调用的时间,但不再是这种情况)。 理由是放松未来标准的规则要比限制它们容易得多。
链接地址: http://www.djcxy.com/p/66587.html上一篇: Why is it ill