2014-09-12 11 views
6

Si tratta di un follow-up a std::unordered_map<T,std::unique_ptr<U>> copyable? GCC bug?modo corretto di fare is_copy_constructible per la resa contenitore falso se il tipo di fondo non è copia costruibili

Quindi immaginiamo abbiamo creato una classe template Container:

template<class T> 
class Container { 
    T t; 
public: 
    Container() = default; 
    Container(const Container& other) : t(other.t) {} 
}; 

Purtroppo, is_copy_constructible per produce true anche se T non è copia costruibile:

static_assert(!std::is_copy_constructible<Container<std::unique_ptr<int>>>::value, "Copyable"); 

Questa affermazione fallisce per i motivi descritti nella risposta alla domanda precedente, anche here is another answer on this topic.

Sembra che questo può essere risolto rendendo il modello copia consructor come questo:

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

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

Questo funziona sia in GCC e clang (static_assert non manca più).

Ideone Demo

Le domande:

  1. Dal punto di vista dello standard, questo è un modo corretto di fare is_copy_constructible lavoro? In caso affermativo, in che modo l'aggiunta del modello influisce sulla validità del contesto immediato dell'inizializzazione della variabile (§20.9.4.3/6)?

  2. (opzionale) Esistono modi più corretti o più intuitivi per eseguire questa operazione?

Nota: la dichiarazione del costruttore di copie default raggiunge anche questo obiettivo, ma non è sempre possibile.

UPDATE: Ora vedo che la mia soluzione non è valida perché il costruttore di copia non può essere un modello. Quella stanza lascia ancora per la domanda 2.

UPDATE 2: ho cambiato un po 'il codice da ecatmur's answer per spostare la bruttezza fuori Container stesso e renderlo riutilizzabile:

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)

Ma ancora non sono abbastanza soddisfatto di questo. Per me sembra un difetto nello standard C++ che cose del genere non funzionino fuori dagli schemi.

+1

Essere consapevoli del fatto che "fare la copia ctor un modello" non dovrebbe realmente funzionare - un'istanza di una funzione di modello non è mai considerata una copia ctor, in modo che il compilatore dovrebbe aggiungere uno se tutto ciò che fornisci è il modello. – Angew

risposta

4

Questo non fa quello che pensi; un costruttore di template non è mai considerato come un costruttore di copie, quindi aggiungendo template<typename U = void> al costruttore di copie si sta inducendo il compilatore a creare il proprio costruttore di copie predefinito.

Una possibilità (a corto di avere modelli di classi separate per i tipi non-copia-costruibile) potrebbe essere quella di disabilitare il costruttore di copia, sostituendo il suo argomento con qualcosa che sarà irrilevante per la risoluzione di sovraccarico:

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

E questo può essere incapsulato in modo che il codice del contenitore stesso non sia così brutto http://ideone.com/B7TFmr –

1

An alternativa alla risposta di ecatmur è la seguente idea, in cui si deriva da una classe di base basata su modelli che è o costruibile in copia o meno a seconda del parametro del modello.

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; 
}; 

Si noti che per questo lavoro, vale a dire per std::is_copy_constructible<container<std::unique_ptr<int>>>::value==false, il costruttore di copia di container deve essere default.

+0

Questo non funziona se il costruttore di copia di 'contenitore' non è predefinito: http://ideone.com/ hJcIFq –

+0

Sì, è corretto. Modificheremo la risposta per farlo notare. – Walter

+1

E se copy ctor è predefinito, in realtà non è necessario, come ho notato nella domanda. Vedi http://ideone.com/9h6dFx –

2

Non esattamente una risposta, tanto quanto un commento dettagliato: uno dei vantaggi di Concepts Lite è la possibilità di limitare le funzioni senza richiedere che siano modelli come nel caso di SFINAE. Concetti Lite renderanno questo problema banale:

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

È bello, ma in realtà non crea un modello di funzione, semplicemente usando una sintassi più semplice? –

+0

@BenVoigt No, questa è una semplice dichiarazione di funzione membro non template. Stai pensando a dichiarazioni di funzioni che usano un nome di concetto o 'auto' al posto di un tipo di parametro (* funzioni generiche * nel lessico dei concetti Lite), ad esempio,' void f (Copiabile c); 'or 'auto f (auto & x) {...} ', che in effetti dichiarano modelli di funzioni. – Casey