2015-04-16 11 views
9

Ich habe eine Klasse, in der ich die Kopier-/Verschiebungszuweisungsoperatoren nur dann aktivieren möchte, wenn ein Typparameter für die Klasse nothrow copy/move constructible ist. So versuche ich dies:enable_if mit Kopieren/Verschieben Zuweisungsoperator

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

Nun Clang gibt mir die folgende Fehlermeldung:

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. 

Nun, ich den expliziten Aufruf den Zuweisungsoperator enthalten die Seltsamkeit des Fehlers zu präsentieren. Es sagt nur, dass meine Vorlage eine Kandidatenfunktion ist und keinen Grund gibt, warum sie nicht gewählt wurde.

Meine Vermutung ist, dass, da ich einen Move-Konstruktor definiert, der Compiler implizit den Kopierzuweisungsoperator gelöscht hat. Da es implizit gelöscht wird, möchte es keine Vorlage instanziieren. Warum es sich so verhält, weiß ich allerdings nicht.

Wie kann ich dieses Verhalten erreichen?

(Anmerkung: Der tatsächliche Code hat berechtigte Gründe jedes der Elemente zu definieren, ich bin mir dessen bewusst, dass es sinnlos ist hier.)

+0

T ist nicht in dem unmittelbaren Zusammenhang mit dem Zuweisungsoperator, so SFINAE nicht auftreten kann. Wenn Sie einen Dummy-Vorlagenparameter zu T hinzufügen, dann ersetzen Sie T für diesen Parameter für den Rest Ihrer Vorlagenargumente, es sollte funktionieren. – 0x499602D2

+2

@ 0x499602D2 Ich kann deine Argumentation nicht ganz durcheinander bringen. Soweit ich das beurteilen kann, besteht das Problem im OP darin, dass Funktionsvorlagen nicht als spezielle Elementfunktionen betrachtet werden (mit Ausnahme des Standard-Ctors). – dyp

+0

* "es will nicht einmal jede Vorlage instantiieren" * Nach allem, was ich weiß, werden Sie mit einem gelöschten 'operator = (Foo const &)' und einer Funktionsschablonenspezialisierung 'operator = (Foo const &)' enden . Und als letzter Ausweg werden Nicht-Template-Funktionen den Spezialisierungen von Funktionsvorlagen vorgezogen - dies wählt den gelöschten Operator aus. – dyp

Antwort

12

Die beste und einzige Weg, dies zu tun ist, über die Regel der Null - - Verwenden Sie die vom Compiler bereitgestellten Zuweisungsoperatoren und Konstruktoren, die jedes der Mitglieder kopieren oder verschieben. Wenn das Mitglied T x nicht kopiert (verschieben) zugewiesen werden kann, wird der Zuweisungsoperator für die Kopie (Verschieben) für Ihre Klasse standardmäßig gelöscht.

Der Grund, warum SFINAE nicht zum Deaktivieren von Kopier- und/oder Verschiebungszuweisungsoperatoren verwendet werden kann, ist, dass SFINAE einen Vorlagenkontext erfordert, aber Kopier- und Verschiebungszuweisungsoperatoren sind Nicht-Template-Elementfunktionen.

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& .

Da Ihre Template-Versionen gelten nicht als Benutzer deklarierte Kopie (move) Zuweisungsoperatoren, werden sie nicht die Erzeugung der es auch keine hemmen, und weil nicht-Vorlagen bevorzugt werden, werden die Standard diejenigen sein, bevorzugt gegenüber Ihren Vorlagendefinitionen (wenn das Argument eine const Foobar& ist, ansonsten ist die Vorlage eine bessere Übereinstimmung, aber das Deaktivieren der Vorlage wird die automatisch generierte nicht deaktivieren).

Wenn Sie zusätzlich zum Aufruf des Zuweisungsoperators kopieren (Verschieben) eine spezielle Logik benötigen, implementieren Sie sie in einem Unterobjekt (Basis oder Mitglied sind beide möglich).


Sie vielleicht Ihr Ziel durch Auswahl von Spezialisierungen einer Klasse Vorlage, die Sie als Basisklasse verwenden, erreichen kann, vorbei an den entsprechenden Typ Züge, wie Sie erben:

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

Die abgeleitete Typ verwenden Die Regel von Null, um die Kopie zu kopieren und zu verschieben, genau dann, wenn die ausgewählte AssignmentEnabler Basisklasse dies tut. Sie müssen AssignmentEnabler für jede der vier Kombinationen spezialisieren (weder kopieren noch verschieben, kopieren ohne verschieben, ohne Kopie kopieren, beide).

vollständige Umsetzung des Codes in Ihrer Frage:

#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

Schön! Einige werden das wahrscheinlich auch nützlich finden: http://scottmeyers.blogspot.se/2012/10/copying-constructors-in-c11.html?m=1 –