std :: is_copy_constructable for std :: vector

问题描述 投票:2回答:1

我最近阅读了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都编译)

现在我有三个问题(对不起,如果它们有点无关):

  1. 此代码是有效的标准C ++,还是在任何地方都依赖未定义的行为?
  2. 您对此想法有何看法?
  3. 我首先为复制构造函数指定了SFINAE条件!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完整性。我不确定可用信息的少量增加是否值得。

c++ templates copy-constructor sfinae incomplete-type
1个回答
1
投票

从逻辑的角度来看也是必要的,因为以下内容对可复制性具有循环依赖性:

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

这在逻辑上没有必要。功能a可以调用功能b,后者又要调用功能a。考虑到前提是必须在Test声明中遇到v声明时回答问题。就我们所知,在当前的C ++中,这是必要的,但这要遵循我们自己施加的各种规则。

此代码是有效的标准C ++,还是在任何地方都依赖未定义的行为?

UB。模板专门化在不同的实例化点上不能具有不同的含义。具体而言,“ ...类模板的静态数据成员可能在转换单元内具有多个实例化点”,其中始终包括结尾temp.point/7。除其他位置外,编译器还可以在转换单元的末尾实例化is_complete<T>::value。如果此程序在不同的实例化点给出不同的答案,则该程序格式不正确。

因此,您无法使用不完整但稍后将完成的类型实例化is_complete,例如Test

© www.soinside.com 2019 - 2024. All rights reserved.