constexpr是否暗示noexcept
Does constexpr imply noexcept?
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;
}
不,情况不可能是这样,因为不是constexpr函数的每个inovacation都必须能够被计算为核心常量表达式的子表达式。我们只需要一个允许这样做的参数值。因此,只要我们有一个不调用该分支的参数值,constexpr函数就可以包含throw语句。
这在草案C++14标准部分7.1.5
中介绍。constexpr说明符[dcl.constexpr]告诉我们constexpr函数中允许的内容:
constexpr函数的定义应满足以下约束:
它不应是虚拟的(10.3);
其返回类型应为文字类型;
其每个参数类型应为文字类型;
其函数体应为=删除、=默认或不包含的复合语句
asm定义,
goto声明
试块,或
非文字类型变量、静态变量或线程存储持续时间变量的定义不执行初始化。
正如我们所看到的,这并没有禁止CCD_。
下面我们看到的规则是,如果至少存在一个参数值,那么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" ;
}
成为(看到它与gcc一起生活):
0
1
Visual Studio通过网络编译器也会产生相同的结果。正如hvd所指出的,clang有一个bug,正如bug报告所记录的那样。noexcept应该检查表达式是否是常量表达式。
缺陷报告1129
缺陷报告1129:constexpr函数的默认nothrow提出了相同的问题:
constexpr函数不允许通过异常返回。应该认识到这一点,并且在没有显式异常规范的情况下声明为constexpr的函数应该被视为声明为noexcept(true),而不是通常的noxcept(false)。对于在没有显式异常规范的情况下声明为constexpr的函数模板,当且仅当给定实例化中遵守constexpr关键字时,应将其视为noexcept(true)。
结果是:
前提是不正确的:只有在需要常量表达式的上下文中调用constexpr函数时,才禁止异常。作为一个普通函数,它可以抛出。
它修改了5.3.7[expr.unary.noexcept]第3段第1项(添加了强调):
函数、成员函数、函数指针或成员函数指针的潜在求值调用80,该调用没有非抛出异常规范(15.4[ixcept.spec]),除非该调用是常量表达式(5.20[expr.const]),
关于noexcept
,有人说:
如果表达式包含对任何没有非抛出异常规范的函数类型的[…]调用,则结果为false,除非它是常量表达式。
此外,关于constexpr
,确实:
对于常量表达式,noexcept运算符总是返回true
在任何情况下,这似乎都不意味着constexpr
说明符强制为被包围的表达式使用noexcept
说明符,正如有人在评论中用反例显示的那样,您也验证了这一点。
不管怎样,从文档中,关于noexcept
和constexpr
之间的关系,有以下有趣的注释:
因为noexcept运算符对于常量表达式总是返回true,所以它可以用于检查constexpr函数的特定调用是否采用常量表达式分支
EDIT: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提供了这个链接,这是关于clang中关于问题中提到的代码的一个众所周知的错误。
引用错误报告:
引用C++的书[expr.unary.noexcept]p3:
"如果在可能求值的上下文中,表达式将包含对没有非抛出异常规范(15.4)的函数、成员函数、函数指针或成员函数指针的可能求值调用,则noexcept运算符的结果为false,除非该调用是常量表达式(5.19)";。
我们不执行最后一句话。
允许在constexpr函数中引发异常。它是这样设计的,以便实现者可以向编译器指示错误。假设您具有以下功能:
constexpr int fast_sqrt(int x) {
return (x < 0) ? throw invalid_domain() : fast_sqrt_impl(x);
}
在这种情况下,如果x为负数,我们需要立即停止编译,并通过编译器错误向用户指示问题。这遵循了编译器错误比运行时错误(快速失败)更好的思想。
C++标准在(5.20):中说明了这一点
条件表达式e是核心常量表达式,除非根据抽象机(1.9),将评估以下表达式之一:
--抛出表达式(5.17)
不,通常不会。
constexpr函数可以在允许抛出异常的非constepr上下文中调用(当然,如果手动将其指定为noexcept(true)
,则除外)。
但是,作为常量表达式的一部分(例如在您的示例中),它的行为应该像指定为noexcept(true)
一样(当然,如果表达式的求值会导致抛出异常,这不会导致对std::terminate
的调用,因为程序尚未运行,而是会导致编译时错误)。
正如我所料,您的示例不会触发MSVC和g++的静态断言。我不确定,这是clang中的错误还是我对标准中的某些内容理解错误。
- 在noexcept 规范中是否允许使用"this"?
- 使用 noexcept 运算符 depenendet
- NOEXCEPT 函数调用运算符的说明符_Not_fn
- 参数包内 noexcept 说明符
- 当 noexcept 函数尝试在 gcc 或 clang 中调用非 noexcept 函数时启用警告
- 为什么使用 std::vector 的代码不能编译,但使用 std::unique_ptr 如果没有 noexcept
- 使用 valgrind 检查我的链表暗示中的内存泄漏,让我"肯定丢失:1 个块中有 40 个字节"
- 我如何静态断言static_cast是 noexcept?
- "noexcept-expression 由于对......的调用而计算为'假'"是什么意思?
- 如何为C++函数指定 noexcept ?
- 如何知道函数何时抛出以及何时使用noexcept
- 添加noexcept是否会破坏二进制兼容性
- noexcept未通过衰变去除
- 为什么旧的空抛规范被用新的语法"noexcept"重写?
- 在模板中使用 noexcept 运算符
- std::is_trivially_xxx 哪个暗示另一个
- 在 vs2012 中,lambda 中的 noexcept 如何工作?
- "inline"、"constexpr"或"noexcept"
- 递归 noexcept() 的含义是什么?
- constexpr是否暗示noexcept