我最近阅读了this blogpost,其中为什么必须无条件地复制vector,以便它可以支持不完整的类型。我理解,从逻辑的角度来看,这也是必要的,因为以下内容对可复制性具有循环依赖性:
struct Test {
std::vector<Test> v;
};
现在,我开始思考,是否至少可以尝试提供可用的最佳信息。换句话说,当且仅当std::vector<T>
是可复制构造的或不完整的,T
是可复制构造的。因此std::vector<std::unique_ptr<T>>
将永远不可复制构造,因为std::unique_vector
是仅移动的,独立于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
都编译)
现在我有三个问题(对不起,如果它们有点无关):
!is_complete_v<T1> || std::is_copy_constructible_v<T1>
,但我不得不添加间接寻址,因为否则,由于std::is_copy_constructible
被不完整类型实例化,因此clang(非gcc)将无法编译。 ||
是否也不会短路模板的实例化?关于1.,我认为应该没有UB。可能发生的一部分是sizeof(T)
,因为不应将其用于不完整的类型。但是使用sizeof
进行SFINAE-ing具有悠久的历史,因为它是唯一一个未评估的上下文,因此我认为这是可以的。
关于2.,我知道这使得vector<T>
是否可复制构造非常脆弱,因为如果在代码的不相关部分的某个地方添加了原本完整的T
的前向声明,然后还要检查完整性,这将更改整个项目的T
完整性。我不确定可用信息的少量增加是否值得。
从逻辑的角度来看也是必要的,因为以下内容对可复制性具有循环依赖性:
struct Test { std::vector<Test> v; };
这在逻辑上没有必要。功能a
可以调用功能b
,后者又要调用功能a
。考虑到前提是必须在Test声明中遇到v声明时回答问题。就我们所知,在当前的C ++中,这是必要的,但这要遵循我们自己施加的各种规则。
此代码是有效的标准C ++,还是在任何地方都依赖未定义的行为?
UB。模板专门化在不同的实例化点上不能具有不同的含义。具体而言,“ ...类模板的静态数据成员可能在转换单元内具有多个实例化点”,其中始终包括结尾temp.point/7。除其他位置外,编译器还可以在转换单元的末尾实例化is_complete<T>::value
。如果此程序在不同的实例化点给出不同的答案,则该程序格式不正确。
因此,您无法使用不完整但稍后将完成的类型实例化is_complete
,例如Test
。