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 中用反例展示的那样,您也已验证。
无论如何,从文档中,关于noexcept
和constexpr
之间的关系有以下有趣的注释:
因为 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?的主要内容,如果未能解决你的问题,请参考以下文章