constexpr 是否意味着 noexcept?

Does constexpr imply noexcept?

constexpr 说明符是否意味着函数的 noexcept 说明符? Answer to the similar question says "yes" concerning inline specifier, but Eric Niebler's article 让我想知道当前问题的可能答案。在我看来,答案可能取决于使用 constexpr 函数的上下文:它是常量表达式上下文还是 运行 时间上下文,即函数的所有参数在编译时是否已知。

我预计答案是 "yes",但 simple check 表明事实并非如此。

constexpr
bool f(int) noexcept
{
    return true;
}

constexpr
bool g(int)
{
    return true;
}

static_assert(noexcept(f(1)));
static_assert(noexcept(g(2))); // comment this line to check runtime behaviour

#include <cassert>
#include <cstdlib>

int
main(int argc, char * [])
{
    assert(noexcept(f(argc)));
    assert(noexcept(g(argc)));
    return EXIT_SUCCESS;
}

It is saidnoexcept 那:

The result is false if the expression contains [...] call to any type of function that does not have non-throwing exception specification, unless it is a constant expression.

此外,关于 constexprit is true

the noexcept operator always returns true for a constant expression

在任何情况下,它似乎都没有暗示 constexpr 说明符强制为包围的表达式使用 noexcept 说明符,正如有人在评论中用反例显示的那样,你也验证了。

无论如何,从文档中,关于 noexceptconstexpr 之间的关系有以下有趣的注释:

Because the noexcept operator always returns true for a constant expression, it can be used to check if a particular invocation of a constexpr function takes the constant expression branch

编辑:GCC 示例

感谢@hvd 对 GCC 关于我最后的引用的有趣 comment/example。

constexpr int f(int i) {
    return i == 0 ? i : f(i - 1);
}

int main() {
    noexcept(f(512));
    return noexcept(f(0)) == noexcept(f(0));
}

returns 0 上面的代码,带有声明 noexcept(f(512)) 无效的警告。
注释掉据说无效的语句,return 值更改为 1.

编辑:clang 上的已知错误

再次感谢@hvd this link,这是关于 clang 中关于问题中提到的代码的众所周知的错误.

引用错误报告:

Quoth the book of C++, [expr.unary.noexcept]p3:

"The result of the noexcept operator is false if in a potentially-evaluated context the expression would contain a potentially-evaluated call to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (15.4), unless the call is a constant expression (5.19)".

We do not implement that last phrase.

不,一般情况下不会。

constexpr 函数可以在允许抛出异常的 non-constepr 上下文中调用(当然,除非您手动将其指定为 noexcept(true))。

但是,作为常量表达式的一部分(例如在您的示例中),它的行为应该就像指定为 noexcept(true) 一样(当然,如果表达式的求值会导致异常抛出这不能导致调用 std::terminate 因为程序还不是 运行,而是导致编译时错误)。

如我所料,您的示例不会触发 MSVC 和 g++ 的静态断言。我不确定这是 clang 中的错误还是我误解了标准中的某些内容。

您可以在 constexpr 函数中抛出异常。它被设计成这样,以便实现者可以向编译器指示错误。假设您具有以下功能:

constexpr int fast_sqrt(int x) {
    return (x < 0) ? throw invalid_domain() : fast_sqrt_impl(x);
}

在这种情况下,如果 x 为负,我们需要立即停止编译并通过编译器错误向用户指出问题。这遵循编译器错误优于运行时错误(快速失败)的想法。

C++ 标准在 (5.20) 中这样说:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:

— a throw-expression (5.17)

不,这不可能是这种情况,因为并非每次对 constexpr 函数的调用都必须能够被评估为核心常量表达式的子表达式。我们只需要一个允许这样做的参数值。所以 constexpr 函数可以包含一个 throw 语句,只要我们有一个不调用该分支的参数值。

这包含在 C++14 标准草案部分 7.1.5 constexpr 说明符 [dcl.constexpr] 告诉我们在 constexpr 函数中允许什么:

The definition of a constexpr function shall satisfy the following constraints:

  • it shall not be virtual (10.3);

  • its return type shall be a literal type;

  • each of its parameter types shall be a literal type;

  • its function-body shall be = delete, = default, or a compound-statement that does not contain

    • an asm-definition,

    • a goto statement,

    • a try-block, or

    • a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed.

正如我们所见,它并不禁止 throw,而且实际上禁止的很少,因为 Relaxing constraints on constexpr functions 提议成为 C++14 的一部分。

下面我们看到规则说 constexpr 函数是 well-formed 如果至少存在一个参数值使得它可以被评估为核心常量表达式的子表达式:

For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.

在这一段下面我们有下面的例子,它是这个案例的完美例子:

constexpr int f(bool b)
  { return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required

所以我们期望以下示例的输出:

#include <iostream>

constexpr int f(bool b)   { return b ? throw 0 : 0; } 

int main() {
    std::cout << noexcept( f(1) ) << "\n" 
              << noexcept( f(0) ) << "\n" ; 
}

成为 (see it live with gcc):

 0
 1

Visual Studio 通过 webcompiler also produces the same result. As hvd noted, clang has a bug as documented by the bug report noexcept should check whether the expression is a constant expression.

缺陷报告 1129

Defect report 1129: Default nothrow for constexpr functions问同样的问题:

A constexpr function is not permitted to return via an exception. This should be recognised, and a function declared constexpr without an explicit exception specification should be treated as if declared noexcept(true) rather than the usual noexcept(false). For a function template declared constexpr without an explicit exception specification, it should be considered noexcept(true) if and only if the constexpr keyword is respected on a given instantiation.

响应是:

The premise is not correct: an exception is forbidden only when a constexpr function is invoked in a context that requires a constant expression. Used as an ordinary function, it can throw.

并修改了 5.3.7 [expr.unary.noexcept] 第 3 段第 1 项( 加注重点 ):

a potentially evaluated call80 to a function, member function, function pointer, or member function pointer that does not have a non-throwing exception-specification (15.4 [except.spec]), unless the call is a constant expression (5.20 [expr.const]),