Правильный способ сделать это copy constructible для контейнера yield false, если базовый тип не является copy constructible
Это продолжение std:: unordered_map > можно скопировать? Ошибка GCC?
Итак, представьте, что мы создали класс шаблонов Container
:
template<class T>
class Container {
T t;
public:
Container() = default;
Container(const Container& other) : t(other.t) {}
};
К сожалению, is_copy_constructible
для него дает true
, Даже если T
не является копией конструктивной:
static_assert(!std::is_copy_constructible<Container<std::unique_ptr<int>>>::value, "Copyable");
Это утверждение терпит неудачу по причинам, описанным в ответе на вопрос выше, также вот еще один ответ на эту тему.
Похоже, что это можно исправить, сделав копию консруктором шаблон такой:
template<class T>
class Container {
T t;
public:
Container() = default;
template<typename U = void>
Container(const Container& other) : t(other.t) {}
};
Это работает как в GCC, так и в clang (static_assert
больше не дает сбоев).
Вопросы:
-
С точки зрения стандарта, является ли это правильным способом заставить
is_copy_constructible
работать? Если да, то как добавление шаблона влияет на валидность непосредственного контекста инициализации переменной (§20.9.4.3/6
)? -
(опционально) есть ли более правильные или более интуитивные как это сделать?
Примечание: объявление конструктора копирования default
также достигает этой цели, но не всегда возможно.
UPDATE: Теперь я вижу, что мое решение недопустимо, потому что конструктор копирования не может быть шаблоном. Это все еще оставляет место для вопроса 2.
Обновление 2: я немного изменил код из ответа экатмура , чтобы переместить уродство из самого Container
и сделать его многоразовые:
struct unused; // forward declaration only
template<class Container>
using const_ref_if_copy_constructible = typename std::conditional<
std::is_copy_constructible<typename Container::value_type>::value,
Container const&,
unused>::type;
template<typename T>
class Container {
T t;
public:
typedef T value_type;
Container() = default;
Container(const_ref_if_copy_constructible<Container> other) : t(other.t) {}
Container(Container&& other) : t(std::move(other.t)) {}
};
Но все же я не вполне удовлетворен этим. Для меня это выглядит как недостаток в стандарте C++, что такие вещи не работают из коробки.3 ответа:
Это не то, что вы думаете; конструктор шаблона никогда не считается конструктором копирования, поэтому, добавляя
template<typename U = void>
к конструктору копирования, вы побуждаете компилятор создать свой собственный конструктор копирования по умолчанию.Возможность (за исключением наличия отдельных шаблонов классов для типов, не поддающихся копированию) состояла бы в отключении конструктора копирования путем замены его аргумента чем-то, что будет неуместно для разрешения перегрузки:
struct unused; // forward declaration only template<typename T> class Container { T t; public: Container() = default; Container( typename std::conditional< std::is_copy_constructible<T>::value, Container const&, unused>::type other) : t(other.t) {} Container(Container&& other) : t(std::move(other.t)) {} };
Не совсем ответ, а скорее подробный комментарий: одним из преимуществ Concepts Lite является возможность ограничивать функции, не требуя, чтобы они были шаблонами, как в случае с SFINAE. Concepts Lite сделает эту проблему тривиальной:
template <typename T> concept bool Copyable = requires(const T source, T dest) { T{source}; // copy construction dest = source; // copy assignment }; template <typename T> class Container { T t; public: Container() = default; Container(const Container& other) requires Copyable<T> : t(other.t) {} };
Альтернативой ответу ecatmur является следующая идея, где вы производны от шаблонного базового класса, который либо может быть сконструирован копией, либо не зависит от параметра шаблона.
Обратите внимание, что для этого, то есть дляtemplate<bool> struct copyable {}; template<> struct copyable<false> { copyable() = default; // default constructible copyable(copyable const&) = delete; // but not copyable }; template<typename T> class container : copyable<std::is_copy_constructible<T>::value> { T t; public: container() = default; container(container const&) = default; };
std::is_copy_constructible<container<std::unique_ptr<int>>>::value==false
, конструктор копированияcontainer
должен бытьdefault
.