【问题标题】:What happens when an exception is thrown while computing a constexpr?在计算 constexpr 时抛出异常会发生什么?
【发布时间】:2014-01-19 03:36:36
【问题描述】:

在计算常量表达式以初始化 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-clause 中的处理程序被执行,但gccclang 都会产生编译时错误。

【问题讨论】:

  • 你最近没有问过类似的问题吗? throw-expression 的求值不是简单地使求值的 constexpr 函数成为 not-a-constant-expression 吗?
  • 我认为[expr.const] / 2适用:条件表达式不是 i>如果它包含“带有参数的@ 987654333函数的调用,则核心常量表达式,当被函数调用替换时,不产生核心常量表达式;"
  • @DyP:我之前询问过throwing from the ternary operator,并考虑到constexprs。但是,当他们被抛出并捕获时,我没有预计会导致编译时间错误:毕竟,永远不会达到constexpr的初始化... span>
  • 我只想说初始化器不是一个常量表达式,因此格式错误。至少我是这么理解的。请注意,[expr.const] 包含一个将 constexpr 函数解析为非常量表达式的示例。
  • 也许我把问题简单化了,但我发现 clang++ 错误test.cpp:17:23: error: constexpr variable 'f42' must be initialized by a constant expression 相当清楚......?

标签: c++ exception c++11 language-lawyer constexpr


【解决方案1】:

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

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

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

一个条件表达式是一个核心常量表达式,除非它涉及以下之一作为可能计算的子表达式,但是[...]条件操作的子表达式不评估不考虑

  • [...]

  • constexpr 函数的调用,其参数在被函数调用替换 (7.1.5) 替换时不会产生常量表达式;

  • [...]

  • 一个抛出表达式 (15.1)。

constexpr 函数的函数调用替换定义如下(第 7.1.5/5 节):

函数调用替换用于调用 constexpr 函数 [...] 意味着隐式地将每个参数转换为相应的参数类型,就像通过复制初始化一样,用转换后的表达式替换每个在 function-body 中使用相应的参数,并且 [...] 将结果返回的表达式或 braced-init-list 隐式转换为函数的返回类型好像通过复制初始化。这种替换不会改变意思。

正如我们在上面看到的(第 5.19/2 节),不考虑未计算的条件操作的子表达式。 f(42) 不是一个常量表达式,因为当您对 f 执行函数调用替换时,它会在计算的条件操作一侧生成一个带有 throw 表达式的表达式。另一方面,对于f(41)throw 最终位于评估的一侧。

所以程序格式不正确。初始化程序是否实际到达并不重要,因为程序不应该编译。

【讨论】:

  • 7.1.5/5 受CWG 1369 的影响(可能还有其他缺陷)。
  • @DyP 值得注意,但我认为这不会影响此解释。
猜你喜欢
  • 1970-01-01
  • 2010-12-12
  • 2011-02-24
  • 1970-01-01
  • 2020-12-03
  • 1970-01-01
  • 2012-11-26
  • 1970-01-01
相关资源
最近更新 更多