【问题标题】:Why can't a destructor be marked constexpr?为什么不能将析构函数标记为 constexpr?
【发布时间】:2017-12-17 17:16:15
【问题描述】:

在 C++ 中,您可以将许多东西声明为 constexpr:变量、函数(包括成员函数和运算符)、构造函数,并且从 C++1z 开始,还可以声明 if statementslambda expressions。但是,声明 destructor constexpr 会导致错误:

struct X {
    constexpr ~X() = default; // error: a destructor cannot be 'constexpr'
};

我的问题:

  1. 为什么不能将析构函数标记为constexpr
  2. 如果我不提供析构函数,是隐式生成的析构函数constexpr
  3. 如果我声明了一个默认析构函数 (~X() = default;),它会自动constexpr吗?

【问题讨论】:

  • 对于 (1),请澄清您是否在询问标准中的哪个位置说析构函数不能是 constexpr;或者如果您询问该规则的基本原理
  • @M.M 我主要感兴趣的是在标准中它说析构函数不能是 constexpr,但也很高兴知道该规则的基本原理。
  • constexpr 对析构函数有用吗?我想不出一个令人信服的例子来让一个程序受益。
  • 一个例子是尝试用填充子类化一个结构,这样填充就被优化掉了(见stackoverflow.com/questions/24110347)。如果将非默认 dtor 添加到基结构(以优化填充),则派生类不能是 constexpr,因为它的 dtor 不会是 constexpr。

标签: c++ language-lawyer constexpr


【解决方案1】:

根据 draft basic.types#10 可能具有以下所有属性的 cv 限定类类型:

具有以下所有属性的可能是 cv 限定的类类型:

(10.5.1) - 它有一个简单的析构函数,

(10.5.2) - 它要么是闭包类型、聚合类型,要么具有 至少一个 constexpr 构造函数或构造函数模板(可能 继承自基类)不是复制或移动构造函数,

(10.5.3) - 如果是联合,至少有一个非静态数据 成员是非易失性文字类型

(10.5.4) - 如果不是 一个联合,它的所有非静态数据成员和基类都属于 非易失性文字类型。

问题一:为什么不能将析构函数标记为 constexpr?

因为只有平凡的析构函数才有资格使用 constexpr 以下是draft的相关部分

如果析构函数不是用户提供的并且满足以下条件,则它是微不足道的:

(5.4) — 析构函数不是虚拟的,

(5.5) — 其类的所有直接基类都具有平凡 析构函数,以及

(5.6) — 对于其类的所有非静态数据成员 类类型(或其数组),每个这样的类都有一个微不足道的 析构函数。

否则,析构函数是不平凡的。

问题2:如果我不提供析构函数,隐式生成的析构函数是constexpr吗?

是的,因为隐式生成的析构函数是普通类型,所以符合constexpr的条件

问题3:如果我声明了一个默认析构函数(~X() = default;),它会自动constexpr吗?

事实上,这个析构函数是用户声明和隐式生成的,因此它符合 constexpr 的条件。


我找不到任何直接引用,只有微不足道的 destructors 符合 constexpr 但如果析构函数不是微不足道的,那么可以肯定类类型不是 cv-qualified. 所以它有点隐含因为您不能为 cv-qualified 类定义 destructor


C++20 更新

自 C++20 起,用户定义的析构函数在某些条件下也可以是 constexpr。

dcl.constexpr/3:

constexpr 函数的定义应满足以下条件 要求:

  • 其返回类型(如果有)应为文字类型;
  • 它的每个参数类型都应该是文字类型;
  • 它不能是协程([dcl.fct.def.coroutine]);
  • 如果函数是构造函数或析构函数,其类不应有任何 虚拟基类;
  • 其函数体不应包含 ([stmt.pre])
    • goto 语句,
    • 一个标识符标签([stmt.label]),
    • 非文字类型或静态或线程变量的定义 存储期限。

【讨论】:

  • 您能否为此声明添加参考? 因为只有平凡的析构函数才有资格使用 constexpr。
  • 如果您可以添加检查此功能是否存在的功能测试宏,那就像肉汁一样。我认为是__cpp_constexpr_dynamic_alloc (P0784R7),但我不能 100% 确定。
【解决方案2】:

如果您要查找的是限制背后的原因,请查看 this paper,它明确指出 限制是人为的 - 析构函数没有阻止它们的内在属性在 constexpr 上下文中工作,并且确实编译器实现者同意在 constexpr 上下文中支持它们将是微不足道的实现

我猜 C++ 标准委员会最初将限制放在 C++11 中是因为他们当时不想处理析构函数,而且完全排除它们更容易。

【讨论】:

    【解决方案3】:

    自 C++20 起,构造函数可以标记为constexpr;我不知道它是否特别指出“析构函数可能是constexpr”,但draft standard 在第 9.2.5 节第 5 段中包含以下文本:

    函数体不是= deleteconstexpr析构函数的定义还应满足 以下要求:

    • 对于类类型或其(可能是多维的)数组的每个子对象,该类类型应 有一个 constexpr 析构函数。

    这现在也有一个有用的功能,因为 C++20 还允许 newdeleteconstexpr 上下文中,允许像 vectorstring 这样的东西在编译时工作而没有 hack(虽然我相信 C++20 实际上并没有包含对标准库的更改以允许这样做,可以实现具有与 vector 相同的 API 和行为的东西,在编译时完全可以工作)。

    【讨论】:

    • 从 C++20 开始,构造函数可能被标记为 constexpr; 哎呀。 Since C++20constructor 都是错字。 (我投票支持后者。);-)
    【解决方案4】:

    为什么不能将析构函数标记为 constexpr?

    C++11 标准专门针对构造函数和非静态成员函数使用constexpr。它没有具体说明析构函数。可以假设析构函数被视为非静态成员函数。

    constexpr 只能用于const 成员函数。由于析构函数不能是const 成员函数,所以它不能被限定为constexpr 成员函数。

    如果我不提供析构函数,是隐式生成的析构函数constexpr

    自从使用

    constexpr ~X() = default;
    

    是一个错误,编译器生成的析构函数不是constexpr 函数对我来说很有意义。我在标准中找不到任何东西来证明我的说法。我猜。

    如果我声明一个默认的析构函数 (~X() = default;),它会自动constexpr

    我认为不会。再一次,我在标准中找不到任何东西来证明我的说法。我猜。


    FWIW,g++ 编译构建以下程序就好了。

    struct X {
       constexpr X(int i) : i_(i) {}
       ~X() = default;
       int i_;
    };
    
    int main()
    {
       const X x(10);
    }
    

    【讨论】:

    • "constexpr 只能用于const 成员函数。"从 C++14 开始,这不再适用。
    【解决方案5】:

    析构函数不能是constexpr,因为constexpr 函数不能有副作用,而根据定义,析构函数只能通过副作用发挥作用。简而言之,使用constexpr 的析构函数是没有用的。

    一个对象不能是constexpr,如果它的析构函数是非平凡的。一个默认的,如果微不足道,将被视为constexpr

    Live

    来自[class.dtor]

    析构函数声明(如果有)的 decl-specifier-seq 的每个 decl-specifier 应为 friendinlinevirtual

    缺少它,constexpr。所以你可以把它当作:因为标准是这样说的TM

    【讨论】:

    • "根据定义,析构函数只有副作用。"不是真的。编写一个没有副作用的析构函数很容易。
    • @M.M 需要详细说明吗?析构函数没有参数,也不返回任何内容,你在它的体内做什么?
    • 任何你喜欢的。主体可以是空的,或者执行一些没有副作用的语句
    • @M.M 你能接受这个改写吗:“析构函数只有通过副作用才有用”
    • constexpr 函数不能有副作用是不正确的。在这里查看吧:godbolt.org/g/LhVGy2
    【解决方案6】:

    Reference 说:

    constexpr 析构函数

    在大多数情况下,为了在常量中创建 T 类型的对象 表达式,T 的破坏一定是微不足道的。然而,不平凡 析构函数是现代 C++ 的重要组成部分,部分原因是 RAII 成语的广泛使用,也适用于 constexpr 评估。可以支持非平凡的析构函数 常量表达式,如下:

    • 允许将析构函数标记为 constexpr
    • 如果默认析构函数只调用 constexpr 析构函数,则将它们设为 constexpr
    • 对于 constexpr 变量,要求对析构函数求值是一个常量表达式(除了被销毁的对象可能是 在自己的析构函数中修改

    但是,对于此类功能,尚无引人注目的用例,并且 将有一个不平凡的实施成本,以确保 析构函数在正确的时间运行。

    【讨论】:

    • 用例不是复杂数学的编译时评估吗?但我确实认为正确实现这样一个特性对编译器程序员来说是一个很大的痛苦,可能不值得拥有。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-09-23
    • 2011-07-03
    • 2021-12-09
    • 2016-12-15
    • 1970-01-01
    • 2016-02-25
    • 1970-01-01
    相关资源
    最近更新 更多