含特定构造函数的C++类型移动构造:跨编译器编译差异问询
先看问题中的代码:
#include <type_traits> class Incomplete; template <class T> struct Templated { T t; }; struct Test { explicit Test(const Templated<Incomplete> &); }; int main() { static_assert(std::is_move_constructible_v<Test>, "Not move constructible"); }
GCC的报错信息如下:
<source>: In instantiation of 'struct Templated<Incomplete>': /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/type_traits:3257:7: required from 'constexpr const bool std::is_move_constructible_v<Test>' <source>:18:24: required from here <source>:9:7: error: 'Templated<T>::t' has incomplete type 9 | T t; | ^ <source>:5:7: note: forward declaration of 'class Incomplete' 5 | class Incomplete; | ^~~~~~~~~~ Compiler returned: 1
Clang的报错本质与GCC一致。而当移除构造函数的const修饰符,或是直接删除该构造函数时,代码能在所有编译器中正常编译。
原因分析
核心问题出在std::is_move_constructible的实现逻辑,以及C++对类型完整性的检查规则上:
自定义构造函数对默认生成函数的影响:当用户声明了自定义构造函数(比如这里的
Test(const Templated<Incomplete>&)),编译器不会自动生成默认的移动构造函数。此时std::is_move_constructible会尝试检查是否可以通过拷贝构造函数来"模拟"移动(这是标准允许的 fallback 逻辑)。const引用触发类型实例化:GCC和Clang的标准库实现中,检查拷贝构造可行性时,需要解析
const Templated<Incomplete>&这个参数的完整类型。由于Templated<Incomplete>包含一个Incomplete类型的成员t,而Incomplete只是前向声明的不完整类型,实例化Templated<Incomplete>时必然会报错——C++不允许类成员为不完整类型。非const引用的特殊处理:如果去掉
const修饰符,参数变为Templated<Incomplete>&,此时编译器检查构造函数可行性时,不需要实例化完整的Templated<Incomplete>类型——前向声明的类型可以直接用于非const引用参数,只要不访问其成员即可。因此std::is_move_constructible的检查不会触发Templated<Incomplete>的实例化,自然不会报错。MSVC的宽松处理:MSVC的标准库实现在
std::is_move_constructible的检查逻辑中没有触发Templated<Incomplete>的实例化,所以能通过编译,但这属于对标准规则的宽松解读,GCC和Clang的行为更符合C++标准的严格要求。
内容的提问来源于stack exchange,提问作者zo_Om

