计算常量表达式以初始化constexpr
时,可能会抛出异常。例如,这里是一个示例,其中常量表达式的计算被防止溢出:
#include <iostream>
#include <stdexcept>
constexpr int g(int n, int n0, int n1) {
return n == 0? n1: g(n - 1, n1, n0 + n1);
}
constexpr int f(int n) {
return n < 42? g(n, 0, 1): throw std::out_of_range("too big");
}
int main()
{
try {
constexpr int f41 = f(41); // OK: constexpr
int f43 = f(43); // OK: throws an exception
constexpr int f42 = f(42); // not OK but what happens?
}
catch (std::exception const& ex) {
std::cout << "ERROR: " << ex.what() << "\n";
}
}
第一次调用f()
只是表明可以计算constexpr
。对f()
的第二次调用不用于初始化constexpr
并抛出运行时异常。对f()
的第三次调用用于初始化constexpr
,但由于抛出异常,因此无法达到该点。但是,在这种情况下会发生什么?我希望catch
- 子句中的处理程序已执行,但gcc和clang都会产生编译时错误。
答案 0 :(得分:12)
constexpr
变量的初始化必须是常量表达式(C ++11§7.1.5/ 9):
对象声明中使用的
constexpr
说明符将对象声明为const
。这样的对象应具有文字类型并应初始化。如果它是由构造函数调用初始化的,[...]。否则,或者如果在引用声明中使用constexpr
说明符,则其初始值设定项中出现的每个完整表达式都应为常量表达式。
请注意常量表达式的以下要求(§5.19/ 2):
条件表达式是一个核心常量表达式,除非它涉及下列其中一个作为潜在评估的子表达式,但子条件表达式为条件运算不评估不被视为
[...]
调用带有参数的
constexpr
函数,当被函数调用替换(7.1.5)替换时,不生成常量表达式;[...]
throw-expression (15.1)。
constexpr
函数的函数调用替换定义如下(第7.1.5 / 5节):
函数调用替换用于调用
constexpr
函数[...]意味着将每个参数隐式转换为相应的参数类型,就像通过复制初始化一样,替换该转换后的表达式对于 function-body 中每个对应参数的使用,以及[...]将生成的返回表达式或 braced-init-list 隐式转换为返回类型该功能就像通过复制初始化一样。这种替代不会改变其含义。
如上所述(§5.19/ 2),未考虑未评估的条件操作的子表达式。 f(42)
不是常量表达式,因为当您在f
上执行函数调用替换时,它会导致在计算的条件运算一侧使用throw
表达式的表达式。另一方面,对于f(41)
,throw
最终会在未评估的一侧。
因此该计划形成不良。是否实际达到初始化并不重要,因为程序不应该编译。