std::is_copy_constructable 对于 std::vector

n31*_*159 5 c++ templates copy-constructor sfinae incomplete-type

我最近阅读了这篇博文,了解为什么向量必须是无条件可复制的,以便它可以支持不完整的类型。我知道从逻辑的角度来看这也是必要的,因为以下内容对可复制性具有循环依赖性:

struct Test {
    std::vector<Test> v;
};
Run Code Online (Sandbox Code Playgroud)

现在我想,是否至少可以尝试提供现有的最佳信息。换句话说,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>);
Run Code Online (Sandbox Code Playgroud)

godbolt (全部static_assert编译)

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

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

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

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

Jef*_*ett 2

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

struct Test {
    std::vector<Test> v;
};
Run Code Online (Sandbox Code Playgroud)

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

该代码是有效的标准 C++ 还是它依赖于任何地方未定义的行为?

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

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