为何V<std::unique_ptr<int>>的不可拷贝赋值断言失败?如何修复?
问题原因分析
1. U<T>断言成功的原因
U<T>中,当T为std::unique_ptr<int>(不可拷贝赋值)时:
- 用户声明的模板版
operator=因std::is_copy_assignable_v<T>为false被SFINAE禁用。 - 编译器尝试生成默认拷贝赋值运算符,但由于成员
t是std::unique_ptr<int>(其拷贝赋值运算符被显式删除),默认拷贝赋值运算符会被隐式删除。 - 最终
U<std::unique_ptr<int>>没有可用的拷贝赋值运算符,std::is_copy_assignable_v返回false,断言成功。
2. V<T>断言失败的原因
V<T>中,当T为std::unique_ptr<int>时:
- 用户声明的模板版
operator=同样被SFINAE禁用。 - 由于模板版
operator=不属于C++标准定义的“用户声明的拷贝赋值运算符”(必须是非模板成员函数),编译器会生成默认拷贝赋值运算符。 - 成员
v是std::vector<std::unique_ptr<int>>:在C++20之前,vector的拷贝赋值运算符签名存在(仅调用时会因元素不可拷贝而编译失败),因此编译器生成的V<T>默认拷贝赋值运算符不会被删除。 std::is_copy_assignable_v仅检查表达式t = u的签名匹配性(不深入函数体),会认为该表达式良构,返回true,导致断言失败。
修复方案
根据不同C++标准,有两种可行的修复方式:
方式一:C++11/17兼容方案
通过声明两个重载的非模板拷贝赋值运算符,结合SFINAE控制可用性:
#include <vector> #include <memory> #include <type_traits> template<typename T> class V { public: // 当T可拷贝赋值时,提供有效的拷贝赋值运算符 typename std::enable_if<std::is_copy_assignable_v<T>, V&>::type operator=(const V& rhs) { v = rhs.v; return *this; } // 当T不可拷贝赋值时,显式删除拷贝赋值运算符 typename std::enable_if<!std::is_copy_assignable_v<T>, V&>::type operator=(const V& rhs) = delete; private: std::vector<T> v; }; template<typename T> class U { public: template<typename U = T, std::enable_if_t<std::is_copy_assignable_v<U>, int> = 0> auto operator = (const U &rhs) -> U & { t = rhs.t; return *this; } private: T t; }; int main() { static_assert(!std::is_copy_assignable_v<std::unique_ptr<int>>); // success static_assert(!std::is_copy_assignable_v<U<std::unique_ptr<int>>>); // success static_assert(!std::is_copy_assignable_v<V<std::unique_ptr<int>>>); // now success return 0; }
方式二:C++20及以上方案
直接使用requires约束简化代码:
#include <vector> #include <memory> #include <type_traits> template<typename T> class V { public: // 仅当T可拷贝赋值时,拷贝赋值运算符才可用 V& operator=(const V& rhs) requires (std::is_copy_assignable_v<T>) { v = rhs.v; return *this; } private: std::vector<T> v; }; template<typename T> class U { public: template<typename U = T, std::enable_if_t<std::is_copy_assignable_v<U>, int> = 0> auto operator = (const U &rhs) -> U & { t = rhs.t; return *this; } private: T t; }; int main() { static_assert(!std::is_copy_assignable_v<std::unique_ptr<int>>); // success static_assert(!std::is_copy_assignable_v<U<std::unique_ptr<int>>>); // success static_assert(!std::is_copy_assignable_v<V<std::unique_ptr<int>>>); // now success return 0; }
修复原理
两种方案均通过阻止编译器生成默认拷贝赋值运算符,并将拷贝赋值运算符的可用性与T的可拷贝赋值性绑定:
- 当
T不可拷贝赋值时,类的拷贝赋值运算符被显式删除或不存在,std::is_copy_assignable_v返回false,断言成功。 - 当
T可拷贝赋值时,拷贝赋值运算符正常可用。
内容的提问来源于stack exchange,提问作者slyx
相关产品推荐
相关产品推荐

