【问题标题】:is_nothrow_default_constructible with a noexcept(false) default constructoris_nothrow_default_constructible 带有 noexcept(false) 默认构造函数
【发布时间】:2018-12-23 14:15:47
【问题描述】:

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

struct DefaultOnly
{
    constexpr DefaultOnly() noexcept(false) {};
};

static_assert(std::is_nothrow_default_constructible_v<DefaultOnly>);

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

【问题讨论】:

    标签: c++ language-lawyer


    【解决方案1】:

    此问题与具有noexcept 规范的构造函数没有直接关系,而是当noexcept 起作用时编译器如何处理常量表达式

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

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

    回到 C++11,常量表达式 不适用于 noexcept 规范,但在 C++17 之前发生了变化。到目前为止,constexpr 函数受noexcept 规范的影响。

    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()));

    CWG 1351(最终以 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-86044GCC-88453 更具体地等同于您的情况。

    【讨论】:

    • 所以 GCC 的人不确定是否要摆脱这种特殊情况,因为它被意外删除了。有趣的。可惜我们现在不能依赖它^^'
    【解决方案2】:

    从 C++17 开始,Clang 是正确的。在那之前,constexpr 会覆盖noexcept(false),因为noexcept 操作员始终是returned true for constant expressions

    【讨论】:

      【解决方案3】:

      std::is_nothrow_default_constructible_v&lt;T&gt;等价于std::is_nothrow_constructible&lt;T&gt;::value,在[meta.unary.prop]中指定为

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

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

      模板特化is_­constructible&lt;T, Args...&gt;的谓词条件当且仅当以下变量定义对于某个发明变量t是格式良好的:

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

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

      noexcept 运算符的结果是 true,除非 表达式 是潜在抛出的。

      根据[except.spec]/6

      一个表达式 e 是潜在的抛出 if

      […]

      • e 隐式调用函数(例如重载运算符、new-expression 中的分配函数、函数参数的构造函数,或者如果 e 是完整表达式,则为析构函数) 是潜在的抛出,或

        […]

      现在,恕我直言,标准中的措辞在这里有点不准确。 std::is_nothrow_default_constructible_v 的值是根据声明语句是否“已知不会引发异常”来指定的,我们参考noexcept 运算符的规范以了解其含义。然而,noexcept 运算符只关心表达式,而我们使用的是声明语句。所以我们有点想知道对于 declaration-statement 的潜在抛出 expression 规范的等价物是什么样子的。我的解释是,标准的意图是在您的情况下要求 std::is_nothrow_default_constructible_vfalse,因为规范中给出的 declaration-statement 将隐式调用带有 的构造函数潜在抛出异常规范

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-06-17
        • 2013-09-10
        • 2018-10-20
        • 2023-03-20
        • 2021-04-07
        • 2015-07-03
        • 2020-05-08
        • 1970-01-01
        相关资源
        最近更新 更多