std::is_copy_constructable 对于 std::vector

std::is_copy_constructable for std::vector

我最近读到 this blogpost 为什么矢量必须是无条件可复制的,这样它才能支持不完整的类型。我理解从逻辑的角度来看这也是必要的,因为以下内容对可复制性具有循环依赖性:

struct Test {
    std::vector<Test> v;
};

现在我想,是否至少可以尝试提供最好的信息。换句话说,当且仅当 T 是可复制构造的或不完整的时,std::vector<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_asserts 编译)

现在我有三个问题(抱歉,如果它们有点不相关):

  1. 此代码是有效的标准 C++ 还是依赖于任何地方的未定义行为?
  2. 你觉得这个想法怎么样?
  3. 我首先为复制构造函数设置了 SFINAE 条件 !is_complete_v<T1> || std::is_copy_constructible_v<T1>,但我必须添加间接寻址,否则 clang(不是 gcc)将不会编译,因为 std::is_copy_constructible 是用不完整的类型实例化的。 ||不也短路模板的实例化吗?

关于1.,我认为应该没有UB。可能发生的部分是 sizeof(T),因为不应该将它与不完整的类型一起使用。但是使用 sizeof 的 SFINAE-ing 有着悠久的传统,因为它是唯一未评估的上下文,所以我认为这没问题。

关于 2.,我知道这使得 vector<T> 是否可复制构造非常脆弱,因为如果在不相关部分的某处添加一个完整的 T 的前向声明的代码,然后也检查它的完整性,这将改变 T 整个项目的完整性。我不确定可用信息的少量增加是否值得。

necessary also from a logical point of view, since the following has a circular dependency on copyability:

struct Test {
    std::vector<Test> v;
};

这在逻辑上没有必要。函数a可以调用函数b,函数b调用函数a。前提是在Test的声明中遇到v的声明必须回答这个问题。在我们所知道的当前 C++ 中,这是必要的,但这是根据我们自己强加的各种规则得出的。

Is this code valid standard C++ or does it rely on undefined behavior anywhere?

UB。模板特化在不同的实例化点不能有不同的含义。具体来说,“... class 模板的静态数据成员可能在翻译单元中具有多个实例化点”,始终包括结尾 temp.point/7。除了其他地方之外,编译器可以在翻译单元的末尾随意实例化 is_complete<T>::value 。如果在不同的实例化点给出不同的答案,则程序格式错误。

因此您不能使用不完整但稍后会完整的类型实例化 is_complete,例如 Test