为什么 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::swap
和 std::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()
。
在 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::swap
和 std::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()
。