is_nothrow_default_constructible具有noexcept(false)默认构造函数

is_nothrow_default_constructible with a noexcept(false) default constructor

本文关键字:false 构造函数 默认 具有 nothrow default constructible is noexcept      更新时间:2023-10-16

当我偶然发现一个奇怪的行为时,我正试图static_assert一些类型特征,以确保自定义类型具有预期的noexcept保证。上面的缩减片段说明了这个问题:

struct DefaultOnly
{
constexpr DefaultOnly() noexcept(false) {};
};
static_assert(std::is_nothrow_default_constructible_v<DefaultOnly>);

对于这个简单的类型,GCC 8通过了static_assert,而Clang 7失败了。我不知道哪个编译器是正确的。这是其中一个编译器中的错误吗?还是另一行默认构造的标准定义足够灵活,以便两个编译器根据对标准的解释产生有效但不同的结果?

这个问题与noexcept规范的构造函数没有直接关系,而是与noexcept运行时编译器如何处理常量表达式有关。

如果您将构造函数声明为无constexpr,那么两个编译器都可以按预期工作:

struct DefaultOnly
{
DefaultOnly() noexcept(false) {};
};
static_assert(std::is_nothrow_default_constructible_v<DefaultOnly>);

回到C++11,常量表达式对于noexcept规范来说是不明智的,但它在C++17之前经历了一些变化。到目前为止,CCD_ 8的功能受到CCD_ 9规范的影响。

Clang按预期工作。

以下代码将显示与您的行为相同的行为:

constexpr int foo() noexcept(false) { return 0;}
static_assert(noexcept(foo()));

作为参考,这是GCC-87603报告的摘录:

CWG 1129(最终在C++11中完成)为noexcept添加了一个特殊情况,用于常量表达式,因此:

constexpr void f() {} static_assert(noexcept(f()));

CWG1351(最终以C++14结束)显著地改变了措辞,但特殊情况仍然以不同的形式存在。

P0003R5(最终在C++17中结束)再次更改了措辞,但特殊情况被删除了(意外),所以现在:

constexpr void f() {} static_assert(!noexcept(f()));

根据Richard Smith在LLVM 15481中的说法,CWG对此进行了讨论,但决定保持原样。目前,clang对C++17做了正确的事情(但对C++14和C++11却故意失败)。然而,g++已经实现了C++11的特殊情况,但没有实现C++17的更改。目前,icc和msvc的行为似乎与g++类似。

另请参阅GCC-86044,GCC-88453更具体地等效于您的案例。

在C++17中,Clang是对的。在此之前,constexpr会覆盖noexcept(false),因为noexcept运算符对于常量表达式总是返回true。

std::is_nothrow_default_constructible_v<T>等效于std::is_nothrow_constructible<T>::value,在[meta.unary.prop]中指定为

is_­constructible_­v<T, Args...>trueis_constructible的变量定义如下所定义,已知不会引发任何异常([expr.unary.noexcept])。

有问题的变量定义在[meta.unary.prop]/8 中给出

模板专门化is_­constructible<T, Args...>的谓词条件应满足,当且仅当以下变量定义对于某些发明的变量t:

T t(declval<Args>()...);

因此,根据标准,如果上述声明语句noexcept运算符的意义上"已知不会抛出异常",则std::is_nothrow_default_constructible_v应为true。来自[expr.unary.noexcept/3]

noexcept运算符的结果为true,除非表达式可能引发。

根据〔except.spec〕/6:

表达式e

[…]

  • e隐式调用一个可能抛出的函数(例如重载运算符、新表达式中的分配函数、函数参数的构造函数,或者如果e是完整表达式则为析构函数),或者

    […]

现在,标准中的措辞有点不精确。std::is_nothrow_default_constructible_v的值是根据声明语句是否"已知不会抛出异常"来指定的,我们将参考noexcept运算符的规范来了解这意味着什么。然而,noexcept运算符只关心表达式,而我们要处理的是声明语句。因此,我们只能猜测,对于声明语句,与潜在抛出表达式的规范等效的内容是什么。我的解释是,在您的情况下,该标准的目的是要求std::is_nothrow_default_constructible_vfalse,因为规范中给出的声明语句将隐式调用具有潜在抛出异常规范的构造函数