2016-12-28 2 views
2
#include <unordered_map> 
#include <type_traits> 

int main() 
{ 
     std::unordered_multimap<int, string> m{ { 1, "hello" } }; 
     auto b = std::is_move_assignable_v<decltype(*m.begin())>; 
     // b is true 

     auto v = *m4.begin(); // ok 
     v = std::move(*m4.begin()); // compile-time error 
} 

Ausgabe:Warum verhält sich [std :: is_move_assignable] nicht wie erwartet?

Wenn b wahr ist, dann sollte v = *m4.begin(); in Ordnung sein.

Frage:

Warum verhält sich std::is_move_assignable nicht wie erwartet?

Fehlermeldungen: (Clang 3.8 + Visual Studio 2015-Update 3)

error : cannot assign to non-static data member 'first' with 
const-qualified type 'const int' 
       first = _Right.first; 
       ~~~~~^

main.cpp(119,5) : note: in instantiation of member function 
'std::pair<const int, std::basic_string<char, std::char_traits<char>, 
std::allocator<char> > >::operator=' requested here 
       v = *m4.begin(); // error 
+2

Es ist wahrscheinlich ein Fehler mit 'std :: is_move_assignable_v' Ihrer Implementierung http://melpon.org/wandbox/permlink/9DNHpi8cKFTF5uEH – Danh

+0

@Danh musste sagen, bevor es ein undefiniertes Verhalten in Beispiel gab, gibt es jetzt gerade keine Direktzugriffsoperator [] auf Multimap. – paweldac

+1

@paweldac Es ist Laufzeit undefiniertes Verhalten, fragte er, warum dieser Code nicht kompiliert werden kann – Danh

Antwort

0

Dereferenzierung .begin() Iterator leeren Behälter ist nicht definiertes Verhalten.

+0

Das ist nicht die Frage. Das Problem ist ein Kompilierungsfehler und kein Laufzeitfehler. Ich habe den Code aktualisiert, um den Fokus nicht zu stören. – xmllmx

+0

@xmllmx Wenn Sie einen Build-Fehler haben, dann sollten Sie * sagen * also, nicht fragen, warum es sich nicht "wie erwartet verhält". Und natürlich die vollständige und vollständige Fehlerausgabe im Fragetext. –

+0

@xmllmx unordered_multimap hat keinen wahlfreien Zugriffsoperator []. Bitte korrigieren Sie Ihren Code erneut. – paweldac

5

Wenn b wahr ist, dann sollte v = *m4.begin(); in Ordnung sein.

v = *m4.begin(); ist Kopierzuordnung, Zuordnung nicht verschieben. Bewegungsaufgabe ist v = std::move(*m4.begin());.

Aber als T.C. weist darauf hin und Yam Markovic antwortete ebenfalls, Ihre Verwendung von std::is_move_assignable ist falsch, weil decltype(*m.begin()) ein Referenztyp ist. Sie am Ende nicht ganz auf Zug Zuweisbarkeit prüfen, und Ihre Prüfung tut Ende rechts für v = *m4.begin();.

Um die Zuordnung zu verschieben, sollte die Überprüfung std::is_move_assignable_v<std::remove_reference_t<decltype(*m.begin())>> sein.

Wie auch immer, is_move_assignable versucht nicht zu schwer zu überprüfen, ob der Typ zuweisbare Bewegung ist, es überprüft nur, ob der Typ sich als zugreifbar zugibt.

Bei

template <typename T> 
struct S { 
    S &operator=(S &&) { T() = "Hello"; return *this; } 
}; 

std::is_move_assignable<S<int>>::valuetrue berichten, da die Signatur S<int> S::operator=(S<int> &&); wohlgeformt ist. Der Versuch, sie tatsächlich aufzurufen, löst einen Fehler aus, da T() nicht zugewiesen werden kann und selbst wenn es möglich wäre, könnte ihm keine Zeichenfolge zugewiesen werden.

In Ihrem Fall ist der Typ std::pair<const int, std::string>. Beachten Sie die const dort.

std::pair<T1, T2> zur Zeit erklärt immer operator=, aber so oder wenn T1 oder T2 kann nicht zugewiesen werden, tatsächlich versucht zu verwenden, es wird einen Fehler erzeugen.

Klassenvorlagen können sicherstellen, dass eine gültige Signatur von operator= nur dann deklariert wird, wenn ihre Instanziierung wohlgeformt sein würde, um dieses Problem zu vermeiden, und wie auch T.C., std::pair will do so in the future zeigt.

+0

Ich habe den Code aktualisiert. Verwenden Sie stattdessen 'std :: move (* m4.begin())' '. – xmllmx

+4

@xmllmx Bitte konzentrieren Sie sich nicht auf einen einzigen Satz. Es ändert nicht den Rest meiner Antwort, die bereits 'v = std :: move (* m4.begin());' abdeckt. – hvd

+0

Dies ist [LWG-Problem 2729] (https://timsong-cpp.github.io/lwg-issues/2729). Auch OP benutzt 'is_move_assignable' falsch: das' declltype' gibt einen Referenztyp zurück. –

3

Zuerst als *begin()returns an lvalue reference, du vorbei tatsächlich einen L-Wert Bezug auf std::is_move_assignable, die dann mittels std::is_assignable<T&, T&&> umgesetzt wird. Beachten Sie nun die Weiterleitungsreferenz, die in eine lvalue-Referenz konvertiert wird. Das bedeutet, dass Sie std::is_move_assignable<SomeType&> fragen std::is_assignable<T&, T&> —, d. H. Kopierbar zu stellen. Zugegeben, das ist ein bisschen irreführend.

Sie können diesen Code testen möchten, um meinen Standpunkt zu sehen:

#include <iostream> 
#include <type_traits> 

struct S { 
    S& operator=(const S&) = default; 
    S& operator=(S&&) = delete; 
}; 

int main() 
{ 
    std::cout << std::is_move_assignable<S>::value; // 0 
    std::cout << std::is_move_assignable<S&>::value; // 1 
} 

Alles, was gesagt, *begin() in diesem Fall liefert ein pair<const int, string>&, so wäre es any-Art-of-zuweisbare ohnehin nicht sein, denn Sie können einem const int nicht zuweisen. Da @ hvd jedoch darauf hinwies, dass die Funktion im Prinzip in der Vorlage pair deklariert ist, versteht der Typmerkmal nicht, dass, wenn er jemals vom Compiler in eine tatsächliche Funktion generiert würde, dieser einen Fehler entdecken würde.

+0

Schön, ich hatte das verpasst und hatte deine Antwort erst am T.C. wies auf das Gleiche hin. Es ist nicht ganz dasselbe wie zuweisbare Kopien. Es ist möglich, aber wahrscheinlich eine sehr schlechte Idee, nur einen 'operator =' overload zu haben, der 'S &' übernimmt, was "S" nicht kopierzuweisbar machen würde. – hvd

Verwandte Themen