std :: is_copy_constructable для std :: vector - PullRequest
3 голосов
/ 08 февраля 2020

Я недавно прочитал этот пост о том, почему вектор должен быть безусловно копируемым, чтобы он мог поддерживать неполные типы. Я понимаю, что это необходимо также с логической точки зрения, поскольку следующее зависит от возможности копирования:

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>, но мне пришлось добавить косвенное обращение, потому что в противном случае лязг (не g cc) не будет компилироваться из-за того, что экземпляр std::is_copy_constructible создается с неполным типом. Разве || также не закорачивает создание шаблонов?

Относительно 1., по моему мнению, не должно быть UB. Единственная часть, где это может произойти, - sizeof(T), так как не следует использовать это с неполным типом. Но SFINA-IN с sizeof имеет давнюю традицию с того момента, когда это был единственный неоцененный контекст, поэтому я думаю, что это нормально.

Что касается 2., я знаю, что это делает * 1038 или нет * является копируемой конструкцией очень быстро agile, так как если добавить некое предварительное объявление в противном случае завершенного T где-нибудь в несвязанной части кода, а затем также проверить его полноту, это изменит полноту T для весь проект. Я не уверен, стоит ли небольшое увеличение доступной информации.

1 Ответ

2 голосов
/ 09 февраля 2020

необходимо также с логической точки зрения, поскольку следующее имеет циклическую зависимость от возможности копирования:

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

Это не делает это логически необходимым. Функция a может вызывать функцию b, которая вызывает функцию a. Это необходимо, если исходить из того, что вы должны ответить на вопрос, когда объявление v встречается внутри объявления Test. В текущем C ++, как мы его знаем, это необходимо, но это следует из различных правил, которые мы сами навязываем.

Является ли этот код допустимым стандартом C ++ или он где-нибудь полагается на неопределенное поведение?

UB. Шаблонные специализации не могут иметь разного значения в разных точках реализации. В частности, «... stati c член данных шаблона класса может иметь несколько точек создания экземпляров в единице перевода», включая всегда конец temp.point / 7 . Компилятор может создать экземпляр is_complete<T>::value в конце модуля перевода, в дополнение к другим местам. Программа плохо сформирована, если она дает другой ответ в разных точках реализации.

Таким образом, вы не можете создать экземпляр is_complete с типом, который является неполным, но позже будет завершен, как Test.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...