我最近阅读了这篇博客文章,讲述了为什么vector必须无条件可复制以支持不完整类型。我理解从逻辑角度来看这是必要的,因为以下内容存在复制的循环依赖:
struct Test {
std::vector<Test> v;
};
现在我考虑是否至少可以尝试提供最佳信息。换言之,std::vector<T>
仅当 T
可以被复制或未完成时才是可复制的。因此,std::vector<std::unique_ptr<T>>
永远不会是可复制的,因为 std::unique_ptr
是只能移动的,与 T
无关。
我得出了以下解决方案:
#include <type_traits>
#include <memory>
template<class T, class = decltype(sizeof(int))>
struct is_complete : std::false_type {};
template<class T>
struct is_complete<T, decltype(sizeof(T))> : std::true_type{};
template<class T>
constexpr bool is_complete_v = is_complete<T>::value;
// Indirection to avoid instantiation of is_copy_constructible with incomplete type
template<class T, class = std::enable_if_t<is_complete_v<T>>>
struct copyable {
static constexpr bool value = std::is_copy_constructible_v<T>;
};
template<class T>
struct copyable<T, void> : std::true_type {};
template<class T>
struct Container {
template<class T1 = T, class = std::enable_if_t<copyable<T1>::value>>
Container(const Container &) {}
};
struct A;
struct B{};
static_assert(!is_complete_v<A>);
static_assert(is_complete_v<B>);
static_assert(std::is_copy_constructible_v<Container<A>>);
static_assert(std::is_copy_constructible_v<Container<B>>);
static_assert(!std::is_copy_constructible_v<std::unique_ptr<A>>);
static_assert(!std::is_copy_constructible_v<std::unique_ptr<B>>);
struct A{};
static_assert(!is_complete_v<A>);
godbolt
(所有 static_assert
均编译通过)
现在我有三个问题(如果它们有点不相关,对不起):
- 这段代码是有效的标准 C++ 代码吗?还是它在任何地方都依赖于未定义的行为?
- 你对这个想法有什么看法?
- 最初,在拷贝构造函数中我使用了 SFINAE 条件
!is_complete_v<T1> || std::is_copy_constructible_v<T1>
,但我不得不添加间接性,因为否则 clang(而不是 gcc)将无法编译,因为std::is_copy_constructible
会实例化一个不完整的类型。||
是否也会短路模板的实例化?
关于第一个问题,我的观点是不应该存在未定义行为。可能发生的一部分是 sizeof(T)
,因为不应该在不完整类型上使用它。但使用 sizeof
进行 SFINAE 检查已经有很长时间了,因为它是唯一的未求值上下文,所以我认为这没问题。
关于第二个问题,我知道这会使得一个 vector<T>
是否可拷贝构造非常脆弱,因为如果在代码的一个不相关部分添加了一个完整定义的 T
的前向声明,然后也检查它的完整性,这将改变整个项目中 T
的完整性。我不确定增加的可用信息是否值得这样。
struct A;
,而另一个则包括不可复制的A
的完整定义。然后在两个单元中使用std::is_copy_constructible_v<Container<A>>
会导致在两个翻译单元中实例化具有两个不同定义的相同类模板,因为例如std::enable_if_t<copyable<T1>::value>
将根据 https://timsong-cpp.github.io/cppwp/n4659/basic.def.odr#6.2 引用不同的模板特化。 - walnut