考虑以下示例:
#include <iostream>
#include <string>
#include <utility>
template <typename Base> struct Foo : public Base {
using Base::Base;
};
struct Bar {
Bar(const Bar&) { }
Bar(Bar&&) = delete;
};
int main() {
std::cout << std::is_move_constructible<Bar>::value << std::endl; // NO
std::cout << std::is_move_constructible<Foo<Bar>>::value << std::endl; // YES. Why?!
}
为什么编译器会生成移动构造函数,尽管基类是不可移动构造的?
这是标准中还是编译器错误?是否有可能“完美地传播”将构造从基础移动到派生类?
答案 0 :(得分:30)
由于:
重载解析会忽略定义为已删除的默认移动构造函数。
([class.copy] / 11)
Bar
移动构造函数已明确删除,因此无法移动Bar
。但由于无法移动Foo<Bar>
成员,Bar
移动构造函数在被隐式声明为默认后被隐式删除 。因此,Foo<Bar>
可以使用其复制构造函数移动。
编辑:我也忘了提到一个重要的事实,即继承构造函数声明(例如using Base::Base
)不会继承默认,复制或移动构造函数,这就是为什么Foo<Bar>
没有#&# 39; t有一个显式删除的移动构造函数,继承自Bar
。
答案 1 :(得分:24)
<强> 1。 std::is_move_constructible
这是std::is_move_constructible的预期行为:
没有移动构造函数但带有接受
const T&
参数的复制构造函数的类型,满足std::is_move_constructible
。
这意味着使用复制构造函数,仍然可以从右值引用T
构造T&&
。 Foo<Bar>
有一个Implicitly-declared copy constructor。
<强> 2。隐式声明的Foo<Bar>
为什么编译器会生成移动构造函数,尽管基类是不可移动构造的?
实际上,Foo<Bar>
的移动构造函数定义为deleted,但请注意,重载解析会忽略已删除的隐式声明的移动构造函数。
类
T
的隐式声明或默认移动构造函数是 定义为以下任何一项中的删除都是真的:... T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors); ...
重载解析会忽略已删除的隐式声明的移动构造函数(否则会阻止rvalue的复制初始化)。
第3。 Bar
和Foo<Bar>
请注意,Bar
的移动构造函数显式声明为deleted
,Foo<Bar>
的移动构造函数是隐式声明的,并定义为deleted
。关键是重载解析忽略了已删除的隐式声明的移动构造函数,这使得可以使用其复制构造函数移动构造Foo<Bar>
。但是显式删除的移动构造函数将参与重载解析,意味着当尝试移动构造函数Bar
时,将选择删除的移动构造函数,然后程序格式不正确。
这就是Foo<Bar>
移动可构建但Bar
不可构建的原因。
该标准对此有明确的陈述。 $12.8/11 Copying and moving class objects [class.copy]
重载解析([over.match],[over.over])忽略定义为已删除的默认移动构造函数。 [注意:删除的移动构造函数否则会干扰rvalue的初始化,而rvalue可以使用复制构造函数。 - 结束说明]