为什么 std::is_move_constructible<S>::value == false 尽管 S 移动构造很好?什么是正确的行为?

Why is std::is_move_constructible<S>::value == false despite S move-constructing just fine? What is the correct behavior?

以下代码通过 const &&.
移动构造 S 然而它returns0,表明S不是可移动构造的!

  1. 根据标准,main 中的 2 个标记结构各自的正确行为是什么?

  2. 如果返回 0 是正确的行为,其背后的基本原理是什么?
    (为什么它不应该反映该类型实际上是否可以通过移动构造?)

#include <algorithm>

struct S
{
    S(          ) { }
    S(S        &) { }  // = delete; doesn't make any difference
    S(S const  &) { }  // = delete; doesn't make any difference
    S(S const &&) { }
    S(S       &&) = delete;
};

int main()
{
    S const s1;
    S s2(std::move(s1));  // OK for >= C++11
    S s3((S()));          // OK for >= C++17, error for <= C++14
    static_assert(std::is_move_constructible<S>::value);
}

虽然 the standard deems S(const S&&) to be "a move constructor" 并且您可能认为这将结束,但 is_move_constructible 实际上要求您对 s3 的声明在 C++14 及更早版本中有效;即,将为该声明选择一个移动构造函数。

From cppref:

If T is not a referenceable type (i.e., possibly cv-qualified void or a function type with a cv-qualifier-seq or a ref-qualifier), provides a member constant value equal to false. Otherwise, provides a member constant value equal to std::is_constructible<T, T&&>::value.

你的问题是S(S&&) can still be found by overload resolution。在发现 S(S&&) 被删除之前,S(S&&)S(const S&&) 更匹配,会发生这种情况。

s3 的声明在 C++17 中有效只是因为根本没有复制或移动进入它。曾经是临时对象的“声明”,现在只是一个奇特的构造函数参数列表;一个匹配的临时文件,当且仅当完全需要一个时,才会在调用堆栈的下方“具体化”。这被称为“保证省略”,即使没有临时对象真正被省略,因为它根本不存在。

记住移动语义是一种幻觉;您真正要做的就是让语言“调用函数”,并为您作为参数提供的表达式提供适当的参数。 s2 之所以有效,是因为您传递了一个 rvalue const S(通过提供 const S&& 形成的表达式的值类别和类型),它可以绑定到一个 const S&&(但 不是 到一个 S&&!);这是否算作“move-constructing just fine”是一个角度问题。

tl;dr:该特性需要 S(S&&) 才能工作,而您明确定义它不需要。