【问题标题】:Why isn't std::swap marked constexpr before C++20?为什么 std::swap 在 C++20 之前没有标记为 constexpr?
【发布时间】:2020-06-25 17:36:37
【问题描述】:

在 C++20 中,std::swap 变为 constexpr 函数。

我知道标准库在标记事物 constexpr 方面确实落后于语言,但到 2017 年,<algorithm> 几乎是 constexpr 和其他一些东西一样。然而 - std::swap 不是。我隐约记得有一些奇怪的语言缺陷阻止了该标记,但我忘记了细节。

有人能简明扼要地解释一下吗?

动机:需要了解为什么在 C++11/C++14 代码中标记类似 std::swap() 的函数 constexpr 可能是个坏主意。

【问题讨论】:

    标签: c++ c++11 constexpr c++20


    【解决方案1】:

    奇怪的语言问题是CWG 1581:

    第 15 条 [特殊] 非常清楚,特殊成员函数仅在使用 odr 时才被隐式定义。这给未计算上下文中的常量表达式带来了问题:

    struct duration {
      constexpr duration() {}
      constexpr operator int() const { return 0; }
    };
    
    // duration d = duration(); // #1
    int n = sizeof(short{duration(duration())});
    

    这里的问题是我们不允许在这个程序中隐式定义constexpr duration::duration(duration&&),所以初始化列表中的表达式不是一个常量表达式(因为它调用了一个尚未定义的 constexpr 函数),所以大括号初始值设定项包含缩小转换,因此程序格式错误。

    如果我们取消注释第 #1 行,则隐式定义了移动构造函数并且程序有效。这种在远处的诡异动作是非常不幸的。实现在这一点上存在分歧。

    您可以阅读问题描述的其余部分。

    2017 年在阿尔伯克基的P0859(在 C++17 发布之后)通过了针对此问题的解决方案。这个问题对于 C++20 都能够同时拥有 constexpr std::swap(在 P0879 中解决)和 constexpr std::invoke(在 P1065 中解决,其中也有 CWG1581 示例)是一个障碍。


    在我看来,这里最容易理解的示例是 P1065 中指出的 LLVM 错误报告中的代码:

    template<typename T>
    int f(T x)
    {
        return x.get();
    }
    
    template<typename T>
    constexpr int g(T x)
    {
        return x.get();
    }
    
    int main() {
    
      // O.K. The body of `f' is not required.
      decltype(f(0)) a;
    
      // Seems to instantiate the body of `g'
      // and results in an error.
      decltype(g(0)) b;
    
      return 0;
    }
    

    CWG1581 是关于when constexpr 成员函数被定义,并且决议确保它们仅在使用时被定义。在P0859之后,上面的格式是良构的(b的类型是int)。

    由于std::swapstd::invoke 都必须依赖检查成员函数(前者中的移动构造/赋值和后者中的调用运算符/代理调用),它们都依赖于这个问题的解决。

    【讨论】:

    • 那么,为什么 CWG-1581 会阻止/不希望将交换函数标记为 constexpr?
    • @einpoklum 交换需要 std::is_move_constructible_v&lt;T&gt; &amp;&amp; std::is_move_assignable_v&lt;T&gt;true。如果尚未生成特殊成员函数,则不会发生这种情况。
    • @NathanOliver:将此添加到我的答案中。
    【解决方案2】:

    原因

    (由于@NathanOliver)

    要允许constexpr 交换函数,您必须在实例化此函数的模板之前检查交换的类型是否可移动构造和可移动分配。不幸的是,由于语言缺陷仅在 C++20 中得到解决,您无法对此进行检查,因为就编译器而言,相关的成员函数可能尚未定义。

    年表

    • 2016:Antony Polukhin 提交提案P0202,将所有&lt;algorithm&gt; 功能标记为constexpr
    • 标准委员会核心工作组讨论缺陷CWG-1581。这个问题使constexpr std::swap()constexpr std::invoke() 成为问题 - 请参阅上面的解释。
    • 2017:Antony revises 多次提出排除 std::swap 和其他一些构造的提议,这已被 C++17 接受。
    • 2017:CWG-1581 问题的解决方案以P0859 提交,并于 2017 年被标准委员会接受(但在 C++17 发布之后)。
    • 2017 年底:Antony 提交补充提案 P0879,在 CWG-1581 决议后制定 std::swap() constexpr。
    • 2018:补充提案被接受 (?) 到 C++20。正如 Barry 指出的那样,constexpr std::invoke() 修复也是如此。

    您的具体情况

    如果您检查移动可构造性和移动可分配性,则可以使用constexpr 交换,而是直接检查类型的其他一些特性,特别是确保这一点。例如只有原始类型,没有类或结构。或者,理论上,您可以放弃检查,只处理您可能遇到的任何编译错误,以及编译器之间的不稳定行为切换。无论如何,不​​要用那种东西代替std::swap()

    【讨论】:

      猜你喜欢
      • 2020-01-20
      • 2013-09-14
      • 2021-09-06
      • 1970-01-01
      • 2017-06-18
      • 2021-10-01
      • 2011-08-30
      • 2021-05-24
      • 1970-01-01
      相关资源
      最近更新 更多