Правильный способ сделать это 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 больше не дает сбоев).

Ideone Demo

Вопросы:

  1. С точки зрения стандарта, является ли это правильным способом заставить is_copy_constructible работать? Если да, то как добавление шаблона влияет на валидность непосредственного контекста инициализации переменной (§20.9.4.3/6)?

  2. (опционально) есть ли более правильные или более интуитивные как это сделать?

Примечание: объявление конструктора копирования 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 6

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.