constexpr是否暗示noexcept?

Ori*_*ent 33 c++ inline noexcept constexpr c++14

constexpr说明noexcept符是否意味着函数的说明符?对类似问题的回答说明者说"是" inline,但Eric Niebler的文章让我想知道对当前问题的可能答案.在我看来,答案取决于使用constexpr函数的上下文:是常量表达式上下文还是运行时上下文,即在编译时是否已知函数的所有参数.

我希望答案是"是",但简单的检查表明情况并非如此.

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;
}
Run Code Online (Sandbox Code Playgroud)

Sha*_*our 25

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

这在C++ 14标准草案章节7.1.5constexpr说明符[dcl.constexpr]中有所介绍,它告诉我们constexpr函数允许的内容:

constexpr函数的定义应满足以下约束:

  • 它不应是虚拟的(10.3);

  • 其返回类型应为字面类型;

  • 每个参数类型都应是文字类型;

  • 它的函数体应为= delete,= default或不包含的复合语句

    • asm-definition,

    • 一个goto声明,

    • 尝试块,或

    • 非文字类型或静态或线程存储持续时间的变量的定义,或者不执行初始化的定义.

我们可以看到它并不禁止throw,事实上禁止很少,因为对constexpr函数提议的放宽限制成为C++ 14的一部分.

下面我们看到规则说如果至少存在一个参数值,则constexpr函数是格式良好的,这样它就可以被计算为核心常量表达式的子表达式:

对于非模板,非默认的constexpr函数或非模板,非默认的,非继承的constexpr构造函数,如果不存在参数值,则调用函数或构造函数可以是核心常量的计算子表达式表达式(5.19),程序不正确 ; 无需诊断.

在本段下面我们有以下示例,其中显示了此案例的完美示例:

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

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

#include <iostream>

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

int main() {
    std::cout << noexcept( f(1) ) << "\n" 
              << noexcept( f(0) ) << "\n" ; 
}
Run Code Online (Sandbox Code Playgroud)

是(看到它与gcc一起生活):

 0
 1
Run Code Online (Sandbox Code Playgroud)

通过webcompiler的 Visual Studio 也会产生相同的结果.正如hvd所指出的那样,clang有一个错误,如bug报告所述,noexcept应检查表达式是否为常量表达式.

缺陷报告1129

缺陷报告1129:constexpr函数的默认值nothrow询问相同的问题:

不允许constexpr函数通过异常返回.这应该被识别,并且声明constexpr而没有显式异常规范的函数应该被视为声明为noexcept(true)而不是通常的noexcept(false).对于声明constexpr而没有显式异常规范的函数模板,当且仅当在给定实例化上遵守constexpr关键字时,才应将其视为noexcept(true).

而回应是:

前提是不正确的:只有在需要常量表达式的上下文中调用constexpr函数时才禁止异常.用作普通函数,它可以抛出.

并修改了5.3.7 [expr.unary.noexcept]第3段子弹1(另外强调指出):

一个可能被评估的函数,成员函数,函数指针或成员函数指针的call80,它没有非抛出异常规范(15.4 [except.spec]),除非调用是一个常量表达式(5.20 [expr.常量]),

  • 运行您提供的 [wandbox 示例](http://melpon.org/wandbox/permlink/ijy45qquzXPHHDYz),现在会生成两个 0(再次按 *run* 按钮即可看到)。自从您第一次编写该测试以来,gcc 中发生了一些变化。您是否知道它是缺陷还是缺陷的修复?这里还有一个关于 godbolt 的例子,比较了 gcc 8.3 和 gcc 9.2 的输出:https://godbolt.org/z/W6-xhB (2认同)

sky*_*ack 5

有人说noexcept是:

如果表达式包含对任何类型的没有非抛出异常规范的函数的调用,则结果为false,除非它是常量表达式.

而且,关于constexpr,确实如此:

对于常量表达式,noexcept运算符始终返回true

在任何情况下,它似乎都没有暗示说明constexpr符强制包含noexcept表达式的说明符,正如有人在评论中显示的反例并且您也验证了.

总之,从文档,有大约之间的关系如下有趣的说明noexceptconstexpr:

因为noexcept运算符总是为常量表达式返回true,所以它可以用于检查constexpr函数的特定调用是否采用常量表达式分支

编辑:GCC的例子

感谢@hvd对GCC关于我的最后一句话的有趣评论/例子.

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

int main() {
    noexcept(f(512));
    return noexcept(f(0)) == noexcept(f(0));
}
Run Code Online (Sandbox Code Playgroud)

上面的代码返回0,并警告该语句noexcept(f(512))无效.
在评论那个据说没有效果的陈述时,返回值会变为1.

编辑:已知的铿锵声

再次感谢@hvd也提供了这个链接,这是关于clang中关于代码中提到的代码的一个众所周知的错误.

从错误报告中引用:

在C++的书中,[expr.unary.noexcept] p3:

"如果在潜在评估的上下文中,表达式将包含对函数,成员函数,函数指针或不具有非抛出异常规范的成员函数指针的潜在评估调用,则noexcept运算符的结果为false (15.4),除非调用是常数表达式(5.19) ".

我们没有实现最后一个短语.