为什么在 C++20 之前 std::swap 没有标记为 constexpr ?

ein*_*ica 14 c++ constexpr c++11 c++20

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

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

有人可以简洁明了地解释这一点吗?

动机:需要理解为什么在 C++11/C++14 代码中标记一个std::swap()-like 函数可能是个坏主意constexpr

Bar*_*rry 11

奇怪的语言问题是CWG 1581

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

struct duration {
  constexpr duration() {}
  constexpr operator int() const { return 0; }
};

// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


在我看来,这里最容易理解的示例是 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;
}
Run Code Online (Sandbox Code Playgroud)

CWG1581 是关于何时定义 constexpr 成员函数的,并且决议确保它们仅在使用时被定义。在P0859之后,上面的就是well-formed(类型bint)。

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

  • @einpoklum swap 要求 `std::is_move_constructible_v&lt;T&gt; &amp;&amp; std::is_move_assignable_v&lt;T&gt;` 为 `true`。如果尚未生成特殊成员函数,则不会发生这种情况。 (3认同)

ein*_*ica 5

原因

(由于@NathanOliver)

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

年表

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

您的具体情况

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