2014-09-12 1 views
6

Это является продолжением до std::unordered_map<T,std::unique_ptr<U>> copyable? GCC bug?Правильный способ сделать is_copy_constructible для урожая контейнера ложно, если базовый тип не копировать конструируемую

Итак, представьте, мы создали шаблон класса 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"); 

Это утверждение не подходит по причинам, описанным в ответе на вопрос выше, также here is another answer on this topic.

Оказывается, что это может быть исправлено путем шаблон копирования consructor как это:

template<class T> 
class Container { 
    T t; 
public: 
    Container() = default; 

    template<typename U = void> 
    Container(const Container& other) : t(other.t) {} 
}; 

Это работает как в GCC и звоном (static_assert не подведет больше).

Ideone Demo

вопросы:

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

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

Примечание: объявление конструктора копии default также достигает этой цели, но не всегда возможно.

UPDATE: Теперь я вижу, что мое решение недействительно, поскольку конструктор копирования не может быть шаблоном. Это все еще оставляет место для вопроса 2.

UPDATE 2: Я немного изменился код с ecatmur's answer двигаться уродство из 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)) {} 
}; 

(Demo)

Но все же я не совсем доволен этим. Для меня это выглядит как недостаток в стандарте C++, что такие вещи не работают из коробки.

+1

Имейте в виде, что «сделать копию т е р шаблон» не должно реально работать - конкретизация шаблона функции никогда не рассматриваются как копия т е р, так что компилятор должен добавить один, если все, что вы предоставляете, является шаблоном. – Angew

ответ

4

Это не то, что вы думаете; конструктор шаблонов никогда не считается конструктором копирования, поэтому, добавив 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)) {} 
}; 
+0

И это может быть инкапсулировано, поэтому сам код контейнера не так уродлив. Http://ideone.com/B7TFmr –

1

An альтернативой ответу 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.

+0

Это не работает, если конструктор копирования 'container' не является значением по умолчанию: http://ideone.com/ hJcIFq –

+0

Да, это правильно. Я исправлю ответ, чтобы указать на это. – Walter

+1

И если копия ctor по умолчанию, это фактически не нужно, как я заметил в вопросе. См. Http://ideone.com/9h6dFx –

2

Не совсем ответ, так как подробный комментарий: одним из преимуществ Concepts Lite является возможность сдерживать функции, не требуя от них шаблонов, как в случае с SFINAE. Концепции 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) {} 
}; 
+0

Это хорошо, но разве он не создает шаблон функции, просто используя простой синтаксис? –

+0

@BenVoigt Нет, это простое старое объявление функции члена без шаблона. Вы думаете о объявлениях функций, которые используют название концепции или 'auto' вместо типа параметра (* общие функции * в лексиконе понятий Lite), например:' void f (Copyable c); 'или' auto f (auto & x) {...} ', которые фактически объявляют шаблоны функций. – Casey