constexpr 是不是暗示 noexcept?

Posted

技术标签:

【中文标题】constexpr 是不是暗示 noexcept?【英文标题】:Does constexpr imply noexcept?constexpr 是否暗示 noexcept? 【发布时间】:2016-04-05 23:29:12 【问题描述】:

constexpr 说明符是否暗示函数的 noexcept 说明符? Answer 到 the similar question 对 inline 说明符说“是”,但 Eric Niebler's article 让我想知道当前问题的可能答案。在我看来,答案可能取决于使用 constexpr 函数的上下文:它是常量表达式上下文还是运行时上下文,即函数的所有参数在编译时是否已知。

我预计答案是“是”,但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;

【问题讨论】:

@cad 无论如何这个问题很笼统,不要认为有很好的具体例子。 反例:constexpr void * foo(int n) return n == 0 ? nullptr : operator new(n); 。 Demo. 我确实滥用过一次,请参阅***.com/a/13305072/34509 这不是真的,对have throws in a constexpr function有效,下面也看我的回答。 查看我的更新答案,我们有一份缺陷报告可以解决这个确切的问题。 【参考方案1】:

It is said 中的noexcept 那个:

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

另外,关于constexpr、it is true 那:

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

在任何情况下,它似乎都不会暗示 constexpr 说明符会强制为被包围的表达式使用 noexcept 说明符,正如有人在 cmets 中用反例展示的那样,您也已验证。

无论如何,从文档中,关于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));

上面的代码返回0,并警告noexcept(f(512))语句无效。 注释掉应该没有效果的语句,返回值变为1

编辑:clang 上的已知错误

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

引用错误报告:

C++之书,[expr.unary.noexcept]p3:

"如果在潜在求值上下文中表达式包含对函数、成员函数、函数指针或没有非抛出的成员函数指针的潜在求值调用,则 noexcept 运算符的结果为假异常规范 (15.4),除非调用是常量表达式 (5.19)"。

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

【讨论】:

也许是您最后引用的 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)); 。这将返回 0,并警告声明 noexcept(f(512)); 无效。注释掉应该没有效果的语句,返回值变为1 @hvd 我想在链接的文档中也可以找到一个例子,无论如何谢谢你,有趣的评论。 至于问题中的代码,that's already known as a bug in clang。我通常会将其作为单独的答案发布,但在这种情况下,我认为它足够小,您可以根据需要将其包含在答案中。 嘿,我的意思是指向 clang 错误报告的链接,但无论哪种方式我都可以。 :) GCC 示例代码为我返回 1(在 GCC 5.2 和 6.3 上测试)。我不明白它怎么会返回 0,因为你是自己的东西,或者它应该说明什么。【参考方案2】:

不,一般情况下不会。

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

但是,作为常量表达式的一部分(例如在您的示例中),它应该表现得好像指定为 noexcept(true) (当然,如果表达式的评估会导致抛出异常,这可以' t 导致调用std::terminate,因为程序尚未运行,而是导致编译时错误)。

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

【讨论】:

如果你已经说noexcept (true)的效果不适用,那它以什么方式暗示noexcept (true) @hvd:正如 skypjack 所引用的:将 noexcept 运算符应用于 constexpr 上下文中的 constexpr 函数(因为问题中的结果为真,就像声明了该函数一样(noexcept(true))。 一个被声明为noexcept(true)的函数有两个作用:第一,它使异常终止程序。二,它使函数调用不影响noexcept 运算符的结果。它没有第一个效果。它的行为方式与第二个效果相似,但不完全相同,无论哪种方式,第二个效果都不是通过隐式创建函数noexcept(true) 来实现的,这是@规则中的一个额外例外987654329@运营商,如@skypjack的回答所示。 @hvd:谢谢,我相应地更改了答案的措辞。有趣的是,当使用 clang 编译时,该示例会触发静态断言,而使用 g++ 或 msvc 编译时则不会。【参考方案3】:

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

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

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

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

条件表达式 e 是核心常量表达式,除非 e 的求值遵循 抽象机器(1.9),将评估以下表达式之一:

——一个 throw 表达式 (5.17)

【讨论】:

您的标准并未完全支持您的答案,您还需要像我在 my answer 中所做的那样引用 7.1.5 以证明这是有效的代码。 @Shafik,我正在寻找具有 constexpr 函数标准的部分。感谢您加入!【参考方案4】:

不,这不可能,因为不是每次调用 constexpr 函数都必须能够作为核心常量表达式的子表达式进行评估。我们只需要一个允许这一点的参数值。所以一个 constexpr 函数可以包含一个 throw 语句,只要我们有一个不调用那个分支的参数值。

这在草案 C++14 标准部分 7.1.5 constexpr 说明符 [dcl.constexpr] 中进行了介绍,它告诉我们 constexpr 函数中允许的内容:

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

不应是虚拟的 (10.3);

它的返回类型应该是一个字面量类型;

它的每个参数类型都应该是文字类型;

它的函数体应该是=delete,=default,或者一个不包含的复合语句

asm 定义,

goto 语句,

一个try-block,或者

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

正如我们所见,它并没有禁止 throw,事实上,自从 Relaxing constraints on constexpr functions 提案成为 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

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

#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 也产生相同的结果。正如 hvd 所指出的,clang 存在错误报告 noexcept should check whether the expression is a constant expression 中记录的错误。

缺陷报告 1129

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

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

得到的回应是:

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

并修改了 5.3.7 [expr.unary.noexcept] 第 3 段第 1 条(添加强调):

对没有非抛出异常规范 (15.4 [except.spec]) 的函数、成员函数、函数指针或成员函数指针的潜在评估调用,除非调用是常量表达式(5.20 [expr.const]),

【讨论】:

运行您提供的wandbox example,现在会产生两个 0 作为结果(再次按 run 按钮可以看到)。自从您第一次编写该测试以来,gcc 发生了一些变化。您是否碰巧知道这是缺陷还是缺陷的修复?这里还有一个关于 godbolt 的例子,它比较了 gcc 8.3 和 gcc 9.2 的输出:godbolt.org/z/W6-xhB

以上是关于constexpr 是不是暗示 noexcept?的主要内容,如果未能解决你的问题,请参考以下文章

将 constexpr 添加到语言后是不是将变量声明为 const 多余?

通过 constexpr 初始化数组

检查 lambda 是不是为 noexcept

将派生实现标记为 noexcept 而接口不是的副作用是啥

基于 constexpr 的计算图灵是不是完整?

函数内的静态 constexpr 变量是不是有意义?