根据Generalized Constant Expressions—Revision 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; }
形式。我看不出有任何理由需要这样做。
在我看来,唯一真正需要的是没有读/写外部状态信息,传入的参数也是'constexpr'语句。这意味着对具有相同参数的函数的任何调用都将返回相同的结果,因此可以在编译时“知道”。
我的主要问题是它似乎只是强迫你做一些真正的回旋形式的循环,并希望编译器优化它,以便它对非constexpr调用一样快。
要为上面的示例写一个有效的constexpr
,你可以这样做:
constexpr int g(int n) // error: body not just ‘‘return expr’’
{
return (n <= 1) ? n : (n * g(n-1));
}
但这很难理解,当你使用违反const-expr
要求的参数调用时,你必须希望编译器负责尾递归。
答案 0 :(得分:17)
原因是编译器已经有很多事情要做,而且还不是一个成熟的解释器,能够评估任意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;
这很难解释。我们需要处理内存读写,我们必须处理return语句。我们的语法树变得复杂得多。我们需要处理变量声明。我们需要处理没有返回值的语句(例如,循环或内存写入),但只是在某处修改某些内存。哪个记忆?哪里?如果它意外地覆盖了一些编译器自己的内存怎么办?如果它是段错误怎么办?
即使没有所有令人讨厌的'假设',编译器必须解释的代码只是让很多更复杂。语法树现在可能如下所示:( 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
操作。
答案 1 :(得分:5)
如果此处存在任何混淆,您会意识到{em}编译时会评估constexpr
个函数/表达式。不涉及运行时性能问题。
知道这一点,他们只允许constexpr
函数中的单个return语句的原因是编译器实现者不需要编写虚拟机来计算常量值。
我担心QoI问题。我想知道编译器实现者是否足够聪明来执行memoization?
constexpr fib(int n) { return < 2 ? 1 : fib(n-1) + fib(n-2); }
如果没有memoization,上面的函数有 O(2 n )的复杂性,即使在编译时,这肯定不是我想要的。
答案 2 :(得分:2)
据我了解,他们保持尽可能简单,以免使语言复杂化(实际上我似乎记得不允许递归调用但不再是这种情况的时间)。理由是,在未来的标准中放松规则比限制规则要容易得多。
答案 3 :(得分:2)
这两种形式都是非法的。由于限定constexpr函数在定义之前无法调用,因此constexpr函数不允许递归。 OP提供的链接明确说明了这一点:
constexpr int twice(int x);
enum { bufsz = twice(256) }; // error: twice() isn’t (yet) defined
constexpr int fac(int x)
{ return x > 2 ? x * fac(x - 1) : 1; } // error: fac() not defined
// before use
进一步向下几行:
常量表达式函数只能调用先前定义的要求 常量表达式函数确保我们不会遇到任何相关的问题 递归。
...
我们(仍)以常量表达式禁止所有形式的递归。
如果没有这些限制,你就会陷入停顿问题(感谢@Grant用我对其他答案的评论来慢慢记忆)。设计师认为简单地说“不”,而不是施加任意的递归限制。
答案 4 :(得分:0)
它可能是形成不良的,因为它实施起来太难了。关于成员函数闭包,在标准的第一个版本中做出了类似的决定(即,能够将obj.func
作为可调用函数传递出去)。也许后来的标准修订版将提供更大的自由度。