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

Why isn't std::swap marked constexpr before C++20?

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

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

谁能简明扼要地解释一下?

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

奇怪的语言问题是CWG 1581

Clause 15 [special] is perfectly clear that special member functions are only implicitly defined when they are odr-used. This creates a problem for constant expressions in unevaluated contexts:

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

// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});

The issue here is that we are not permitted to implicitly define constexpr duration::duration(duration&&) in this program, so the expression in the initializer list is not a constant expression (because it invokes a constexpr function which has not been defined), so the braced initializer contains a narrowing conversion, so the program is ill-formed.

If we uncomment line #1, the move constructor is implicitly defined and the program is valid. This spooky action at a distance is extremely unfortunate. Implementations diverge on this point.

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

此问题的解决方案已在 P0859 in Albuquerque in 2017 (after C++17 shipped). That issue was a blocker for both being able to have a constexpr std::swap (resolved in P0879) and a constexpr std::invoke (resolved in 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;
}

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

由于 std::swapstd::invoke 都必须依赖于检查成员函数(前者移动 construction/assignment 后者调用 operator/surrogate 调用),他们都取决于这个问题的解决。

原因

(由于@NathanOliver)

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

年表

  • 2016 年:Antony Polukhin 提交提案 P0202,将所有 <algorithm> 功能标记为 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()