Я недавно прочитал этот пост о том, почему вектор должен быть безусловно копируемым, чтобы он мог поддерживать неполные типы. Я понимаю, что это необходимо также с логической точки зрения, поскольку следующее зависит от возможности копирования:
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
скомпилированы)
Теперь у меня есть три вопроса (извините, если они немного не связаны):
- Этот код является допустимым стандартом C ++ или делает это полагаться на неопределенное поведение в любом месте?
- Что вы думаете об этой идее?
- Сначала у конструктора копирования было условие 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
для весь проект. Я не уверен, стоит ли небольшое увеличение доступной информации.