以下面的代码为例:
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))
。
因此,我了解了为什么右值从迁移到左值复制“降级”,以及为什么S::S(const S&)
返回true。但是,有没有一种方法可以检测类型(复制构造函数除外)是否真正可移动构造?
答案 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而不会出现错误。