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


14

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

我知道标准库在标记事物方面确实落后于该语言constexpr,但是到了2017年,<algorithm>它和许多其他事物一样已经成为constexpr了。但是- std::swap不是。我隐约记得有一些奇怪的语言缺陷阻止了这种标记,但是我忘记了细节。

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

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

Answers:


11

奇怪的语言问题是CWG 1581

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

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行,则将隐式定义move构造函数,并且该程序有效。远距离的这种怪异的动作是非常不幸的。在这一点上,实现方式有所不同。

您可以阅读问题说明的其余部分。

2017年在Albuquerque的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;
}

CWG1581涉及何时定义constexpr成员函数,并且分辨率可确保仅在使用时定义它们。在P0859之后,以上内容格式正确(类型bint)。

由于std::swap并且std::invoke两者都必须依靠检查成员功能(在前者中移动构造/分配,在后者中移动呼叫操作者/代理呼叫),因此它们都依赖于此问题的解决方案。


那么,为什么CWG-1581阻止/不希望将交换功能标记为constexpr?
einpoklum

3
@einpoklum交换要求std::is_move_constructible_v<T> && std::is_move_assignable_v<T>true。如果尚未生成特殊成员函数,则不会发生这种情况。
NathanOliver

@NathanOliver:将此添加到我的答案中。
einpoklum

5

原因

(由于@NathanOliver)

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

年表

  • 2016年:Antony Polukhin提交了提案P0202,将所有<algorithm>功能标记为constexpr
  • 标准委员会的核心工作组讨论了缺陷CWG-1581。这个问题使它变得有问题,constexpr std::swap()并且constexpr std::invoke()-参见上面的说明。
  • 2017年:Antony 几次修改了他的提议以排除std::swap其他构造,这被C ++ 17接受。
  • 2017年:CWG-1581问题的决议以P0859的形式提交,并在2017年被标准委员会接受(但在C ++ 17发行后)。
  • 2017年底:Antony提交了一份补充提案P0879,以std::swap()在CWG-1581决议后制造constexpr。
  • 2018年:补充建议被C ++ 20接受(?)正如Barry指出的那样,constexpr std::invoke()修复程序也是如此。

您的具体情况

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.