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

Meh*_*dad 3 c++ move-constructor move-semantics c++11 c++17

以下代码S通过const &&.
然而它返回0,表明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);
}
Run Code Online (Sandbox Code Playgroud)

Ast*_*ngs 9

尽管标准认为S(const S&&)是“移动构造函数”并且您可能认为这将是它的结束,但is_move_constructible实际上要求您的声明s3在 C++14 及更早版本中有效;即将为该声明选择一个移动构造函数。

从 cppref

如果T不是可引用类型(即,可能是cv 限定的 void或具有cv-qualifier-seqref-qualifier的函数类型),则提供等于 的成员常量值false。否则,提供等于 的成员常量值std::is_constructible<T, T&&>::value

您遇到的问题是S(S&&)仍然可以通过重载解析找到。发生这种情况,S(S&&)S(const S&&),更好的匹配S(S&&)被发现被删除。

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

请记住,移动语义是一种错觉;您真正要做的就是让语言“调用函数”,并为您作为参数提供的表达式使用适当的参数。s2之所以有效,是因为您传递了一个右值 const S(通过提供 a 形成的表达式的值类别和类型const S&&),它可以绑定到 a const S&&(但不能绑定到 a S&&!);这是否算作“移动构建就好”是一个观点问题。

tl; dr:该特征需要S(S&&)起作用,而您明确定义了它不能起作用。