2015-04-16 3 views
9

Ho una classe in cui desidero abilitare gli operatori di assegnazione copia/spostamento solo se un parametro di tipo per la classe è rispettivamente copia/sposta costruttibili. Così ho prova questo:enable_if con operatore di assegnazione copia/spostamento

#include <type_traits> 

template<typename T> 
struct Foobar { 

    Foobar(T value) : x(value) {} 
    Foobar(const Foobar &other) : x(other.x) {} 
    Foobar(Foobar &&other) : x(std::move(other.x)) {} 

    template<bool Condition = std::is_nothrow_copy_constructible<T>::value, 
      typename = typename std::enable_if<Condition>::type> 
    Foobar &operator=(const Foobar &rhs) { 
     x = rhs.x; 
     return *this; 
    } 

    template<bool Condition = std::is_nothrow_move_constructible<T>::value, 
      typename = typename std::enable_if<Condition>::type> 
    Foobar &operator=(Foobar &&rhs) { 
     x = std::move(rhs.x); 
     return *this; 
    } 

    T x; 
}; 

int main() { 
    Foobar<int> foo(10); 
    Foobar<int> bar(20); 

    foo = bar; 
    foo.operator=(bar); 

    return 0; 
} 

Ora, Clang mi dà il seguente errore:

enable_if_test.cpp:31:9: error: object of type 'Foobar<int>' cannot be assigned because its copy assignment operator is implicitly 
     deleted 
    foo = bar; 
     ^
enable_if_test.cpp:8:5: note: copy assignment operator is implicitly deleted because 'Foobar<int>' has a user-declared move 
     constructor 
    Foobar(Foobar &&other) : x(std::move(other.x)) {} 
    ^
enable_if_test.cpp:32:9: error: call to deleted member function 'operator=' 
    foo.operator=(bar); 
    ~~~~^~~~~~~~~ 
enable_if_test.cpp:4:8: note: candidate function (the implicit copy assignment operator) has been implicitly deleted 
struct Foobar { 
    ^
enable_if_test.cpp:12:13: note: candidate function [with Condition = true, $1 = void] 
    Foobar &operator=(const Foobar &rhs) { 
      ^
enable_if_test.cpp:19:13: note: candidate function [with Condition = true, $1 = void] not viable: no known conversion from 
     'Foobar<int>' to 'Foobar<int> &&' for 1st argument 
    Foobar &operator=(Foobar &&rhs) { 
      ^
2 errors generated. 

Ora, ho inserito il richiamo esplicito alla operatore di assegnazione per mostrare la stranezza dell'errore. Dice solo che il mio modello è una funzione candidata e non fornisce motivi per cui non è stato scelto.

La mia ipotesi è che poiché ho definito un costruttore di spostamenti, il compilatore ha implicitamente eliminato l'operatore di assegnazione delle copie. Poiché è implicitamente cancellato, non vuole neanche istanziare alcun modello. Perché si comporta in questo modo non lo so, comunque.

Come posso ottenere questo comportamento?

(Nota: il codice vero e proprio ha motivi legittimi per definire ciascuno dei membri, mi rendo conto che è inutile qui.)

+0

T non è nel contesto immediato dell'operatore di assegnazione, in modo da SFINAE non può verificarsi. Se si aggiunge un parametro del modello fittizio predefinito a T, quindi sostituire T con tale parametro per il resto degli argomenti del modello, dovrebbe funzionare. – 0x499602D2

+2

@ 0x499602D2 Non riesco a deludere la tua argomentazione. Per quanto ne so, il problema nell'OP è che i modelli di funzione non sono considerati come funzioni membro speciali (ad eccezione del ctor predefinito). – dyp

+0

* "non vuole neanche istanziare alcun modello" * Per quello che ne so, si finirà con un operatore 'cancellato = (Foo const &)' e un operatore di specializzazione del modello di funzione = (Foo const &) ' . E come ultima risorsa, le funzioni non template sono preferite rispetto alle specializzazioni dei modelli di funzione: questo seleziona l'operatore cancellato. – dyp

risposta

12

Il modo migliore e solo per farlo è tramite la regola dello zero - - utilizzare gli operatori ei costruttori di assegnamento forniti dal compilatore, che copiano o spostano ciascuno dei membri. Se il membro T x non può essere assegnato alla copia (sposta), l'operatore di assegnazione della copia (spostamento) della classe verrà automaticamente eliminato.

Il motivo per cui SFINAE non può essere utilizzato per disabilitare gli operatori di copia e/o spostamento è che SFINAE richiede il contesto del modello, ma gli operatori di assegnazione e copia non sono membri del modello.

A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X , X& , const X& , volatile X& or const volatile X& .

Dal momento che le versioni del modello non contano come copia (MOVE) operatori di assegnazione dall'utente dichiarato, essi non inibiscono la generazione di quelli di default, e perché non i modelli sono preferiti, quelli di default saranno preferito rispetto alle definizioni del modello (quando l'argomento è un const Foobar&, altrimenti il ​​modello è una corrispondenza migliore, ma la disattivazione del modello non disabiliterà comunque quella generata automaticamente).

Se è necessaria una logica speciale oltre alla chiamata all'operatore di assegnazione copia (sposta) del membro, implementarla in un sottotitolo (base o membro sono entrambi fattibili).


Si può forse raggiungere il tuo obiettivo scegliendo tra specializzazioni di un modello di classe che si utilizza come una classe base, passando i tratti tipo appropriato come si eredita:

template<bool allow_copy_assign, bool allow_move_assign> 
struct AssignmentEnabler; 

template<typename T> 
struct Foobar : AssignmentEnabler<std::is_nothrow_copy_constructible<T>::value, 
            std::is_nothrow_move_constructible<T>::value> 
{ 
}; 

Il tipo derivato useranno la regola dello zero per impostazione predefinita per avere l'assegnazione di copia e spostamento se e solo se la classe di base AssignmentEnabler selezionata lo fa. È necessario specializzarsi su AssignmentEnabler per ciascuna delle quattro combinazioni (né copia né sposta, copia senza spostamento, sposta senza copia, entrambe).

conversione completa del codice nel tuo domanda:

#include <type_traits> 

template<bool enable> 
struct CopyAssignmentEnabler {}; 

template<> 
struct CopyAssignmentEnabler<false> 
{ 
    CopyAssignmentEnabler() = default; 
    CopyAssignmentEnabler(const CopyAssignmentEnabler&) = default; 
    CopyAssignmentEnabler(CopyAssignmentEnabler&&) = default; 
    CopyAssignmentEnabler& operator=(const CopyAssignmentEnabler&) = delete; 
    CopyAssignmentEnabler& operator=(CopyAssignmentEnabler&&) = default; 
}; 

template<bool enable> 
struct MoveAssignmentEnabler {}; 

template<> 
struct MoveAssignmentEnabler<false> 
{ 
    MoveAssignmentEnabler() = default; 
    MoveAssignmentEnabler(const MoveAssignmentEnabler&) = default; 
    MoveAssignmentEnabler(MoveAssignmentEnabler&&) = default; 
    MoveAssignmentEnabler& operator=(const MoveAssignmentEnabler&) = default; 
    MoveAssignmentEnabler& operator=(MoveAssignmentEnabler&&) = delete; 
}; 

template<typename T> 
struct Foobar : CopyAssignmentEnabler<std::is_nothrow_copy_constructible<T>::value>, 
       MoveAssignmentEnabler<std::is_nothrow_move_constructible<T>::value> 
{ 
    Foobar(T value) : x(value) {} 

    T x; 
}; 

int main() { 
    Foobar<int> foo(10); 
    Foobar<int> bar(20); 

    foo = bar; 
    foo.operator=(bar); 

    return 0; 
} 
+0

Bello! Alcuni probabilmente troveranno utile anche questo: http://scottmeyers.blogspot.se/2012/10/copying-constructors-in-c11.html?m=1 –