当计算constexpr时抛出异常会发生什么?

16

在计算用于初始化constexpr的常量表达式时,有可能会抛出异常。例如,下面是一个例子,其中计算常量表达式被防止溢出:

当计算用于初始化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子句中执行处理程序,但是gccclang都会产生编译时错误。


2
你最近没有问过类似的问题吗?难道抛出表达式的评估不仅会使评估的constexpr函数成为非常量表达式吗? - dyp
我认为适用[expr.const]/2:如果条件表达式包含“调用constexpr函数的参数,当通过函数调用替换时不产生核心常量表达式”,则条件表达式不是核心常量表达式。 - dyp
@DyP:我之前曾询问过关于在考虑constexpr的情况下从三元运算符中抛出异常的问题(https://dev59.com/kWEi5IYBdhLWcg3w6PvZ)。然而,当异常被抛出和捕获时,我没有预料到会导致编译时错误:毕竟,`constexpr`的初始化永远不会被执行... - Dietmar Kühl
我只能说初始化器不是常量表达式,因此是不合法的。至少这是我的理解。请注意,[expr.const]中包含一个示例,其中constexpr函数被解析为非常量表达式。 - dyp
3
也许我过分简化这个问题,但我认为clang++错误test.cpp:17:23: error: constexpr variable 'f42' must be initialized by a constant expression相当明显...? - Joachim Isaksson
@JoachimIsaksson:我理解错误信息。然而,由于在评估“constexpr”变量的初始化程序时抛出了异常,因此代码永远不会进行初始化。我并不是说产生错误而不是异常是错误的(我甚至认为我更喜欢这种方式),但我仍然认为它很奇怪。 - Dietmar Kühl
1个回答

13
一个 constexpr 变量的初始化器必须是一个常量表达式(C++11 §7.1.5/9):

在对象声明中使用 constexpr 指定符将其声明为 const。这样一个对象应该具有字面类型并被初始化。如果它通过构造函数调用进行初始化,[...]。否则,或者如果在引用声明中使用了 constexpr 指定符,则出现在其初始化器中的每个完整表达式都应该是一个常量表达式。

请注意以下常量表达式的要求(§5.19/2):

除了涉及以下内容的潜在评估子表达式之一以外,条件表达式是一个核心常量表达式,但不考虑未经评估的条件操作的子表达式

  • [...]

  • 具有实参并且通过函数调用替换(7.1.5)时不产生常量表达式的 constexpr 函数的调用;

  • [...]

  • 一个 throw-expression (15.1)。

对于 constexpr 函数的函数调用替换定义如下(§7.1.5/5):

对于 constexpr 函数的调用,函数调用替换 [...] 意味着将每个实参隐式转换为相应的参数类型,就像通过复制初始化一样,将该转换表达式代替函数体中对应参数的每个使用,并[...] 隐式转换生成的返回表达式或 大括号初始化列表 到函数返回类型,就像通过复制初始化一样。这种替换不会改变含义。

如上面所见(§5.19/2),未被评估的条件操作子表达式不予考虑。 f(42)不是常数表达式,因为当您对f执行函数调用替换时,结果是在被实际评估的条件操作一侧含有一个throw表达式的表达式。 另一方面,对于f(41)throw会出现在未被评估的那一侧。

因此,程序是不合规的。无论初始化器是否实际到达,程序都不应该编译。


7.1.5/5受CWG 1369(以及可能的其他缺陷)的影响。 - dyp
@DyP 很好注意到,但我不认为它会影响这个解释。 - Joseph Mansfield

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接