如果类型真正可移动,如何获取

时间:2018-08-17 19:33:18

标签: c++ stl move-semantics typetraits

以下面的代码为例:

Intervention

因为Bar派生自Foo,所以它没有move构造函数。通过使用复制构造函数仍然可以构造它。我了解了为什么它从另一个答案中选择了复制构造函数:

  

如果#include <type_traits> #include <iostream> struct Foo { Foo() = default; Foo(Foo&&) = delete; Foo(const Foo&) noexcept { std::cout << "copy!" << std::endl; }; }; struct Bar : Foo {}; static_assert(!std::is_move_constructible_v<Foo>, "Foo shouldn't be move constructible"); // This would error if uncommented //static_assert(!std::is_move_constructible_v<Bar>, "Bar shouldn't be move constructible"); int main() { Bar bar {}; Bar barTwo { std::move(bar) }; // prints "copy!" } 的类型为y,则S的类型std::move(y)与类型S&&兼容。因此S&是完全有效的,并调用复制构造函数S x(std::move(y))

     

—Lærne,Understanding std::is_move_constructible

因此,我了解了为什么右值从迁移到左值复制“降级”,以及为什么S::S(const S&)返回true。但是,有没有一种方法可以检测类型(复制构造函数除外)是否真正可移动构造?

2 个答案:

答案 0 :(得分:3)

有人声称presence of move constructor can't be detected并从表面上看似乎是正确的-&&绑定到const&的方式使得无法分辨类的接口中存在哪些构造函数。

然后我想到了-在C ++中移动语义不是单独的语义...它是复制语义的“别名”,是类实现者可以“拦截”并提供替代实现的另一个“接口”。因此,问题“我们可以检测到存在移动ctor吗?”可以重新定义为“我们可以检测到两个复制接口的存在吗?”。事实证明,我们可以通过(ab)使用重载来实现这一点-当有两种同样可行的方式构造对象时,它无法编译,并且可以使用SFINAE来检测到这一事实。

30 lines of code值一千字:

#include <type_traits>
#include <utility>
#include <cstdio>

using namespace std;

struct S
{
    ~S();
    //S(S const&){}
    //S(S const&) = delete;
    //S(S&&) {}
    //S(S&&) = delete;
};

template<class P>
struct M
{
    operator P const&();
    operator P&&();
};

constexpr bool has_cctor = is_copy_constructible_v<S>;
constexpr bool has_mctor = is_move_constructible_v<S> && !is_constructible_v<S, M<S>>;

int main()
{
    printf("has_cctor = %d\n", has_cctor);
    printf("has_mctor = %d\n", has_mctor);
}

注意:

  • 您可能应该能够将此逻辑与其他const/volatile重载混淆,因此此处可能需要做一些额外的工作

  • 怀疑此魔术在私有/受保护的构造函数中效果很好-另一个需要研究的领域

  • 似乎在MSVC上不起作用(传统上如此)

答案 1 :(得分:0)

如何确定类型是否具有移动构造函数?

假设基类来自上游,而派生类是应用程序的一部分,一旦决定从“他们的” Foo中派生“您的”酒吧,您将没有进一步的决定。

基类Foo负责定义其自己的构造函数。这是基类的实现细节。派生类也是如此。构造函数不是继承的。琐碎地,这两个类都完全控制自己的实现。

因此,如果要在派生类中具有move构造函数,只需添加一个:

struct Bar : Foo {
   Bar(Bar&&) noexcept {
      std::cout << "move!" << std::endl;
   };
};

如果您不需要任何内容​​,请将其删除:

struct Bar : Foo {
   Bar(Bar&&) = delete;
};

如果您选择后者,则也可以取消注释第二个static_assert而不会出现错误。