如何获取类型是否真正可移动可构造

How to get if a type is truly move constructible

本文关键字:可移动 何获 取类型 是否      更新时间:2023-10-16

以这段代码为例:

#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!"
}

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

如果yS型,则S&&型的std::move(y)S&型的引用兼容。 因此S x(std::move(y))是完全有效的,并调用复制构造函数S::S(const S&)

——莱恩,《理解std::is_move_constructible

所以我理解为什么右值从移动到左值复制"降级",因此为什么std::is_move_constructible返回 true。但是,有没有办法检测类型是否真的是可移动的,不包括复制构造函数?

有人声称无法检测到移动构造函数的存在,表面上它们似乎是正确的 -&&绑定到const&的方式使得无法分辨类接口中存在哪些构造函数。

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

30行代码胜过千言万语:

#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 = %dn", has_cctor);
printf("has_mctor = %dn", has_mctor);
}

笔记:

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

  • 怀疑这种魔术适用于私有/受保护的构造函数 - 另一个需要关注的领域

  • 似乎不适用于 MSVC(这是传统(

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

假设基类来自上游,并且派生类是应用程序的一部分,一旦您决定从"他们的"Foo派生"您的"Bar,就无法做出进一步的决定。

基类 Foo 负责定义自己的构造函数。这是基类的实现细节。派生类也是如此。构造函数不是继承的。简单来说,这两个类都可以完全控制自己的实现。

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

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

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

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

如果执行后者,还可以取消注释第二个static_assert而不会出错。