2013-02-16 10 views
10

Werfen Sie einen Blick auf den folgenden Code:std :: map <> :: insert mit nicht-kopierbaren Objekten und einheitliche Initialisierung

#include <utility> 
#include <map> 

// non-copyable but movable 
struct non_copyable { 
    non_copyable() = default; 

    non_copyable(non_copyable&&) = default; 
    non_copyable& operator=(non_copyable&&) = default; 

    // you shall not copy 
    non_copyable(const non_copyable&) = delete; 
    non_copyable& operator=(const non_copyable&) = delete; 
}; 

int main() { 
    std::map<int, non_copyable> map; 
    //map.insert({ 1, non_copyable() }); < FAILS 
    map.insert(std::make_pair(1, non_copyable())); 
    //^same and works 
} 

diese Schnipsel kompilieren schlägt fehl, wenn die markierte Zeile auf g uncommenting ++ 4.7. Der erzeugte Fehler zeigt an, dass non_copyable nicht kopiert werden kann, aber ich habe erwartet, dass es verschoben wird.

Warum schlägt das Einfügen einer std::pair, die mit uniformer Initialisierung erstellt wurde, fehl, aber nicht eine, die mit std::make_pair erstellt wurde? Sollen nicht beide Rvalues ​​erzeugen, die erfolgreich in die Map verschoben werden können?

Antwort

17

[Dies ist eine komplette Neuschreibung. Meine frühere Antwort hatte nichts mit dem Problem zu tun]

Die map hat zwei relevante insert Überlastungen.

  • insert(const value_type& value) und

  • <template typename P> insert(P&& value).

Wenn Sie die einfachen Liste-initializer map.insert({1, non_copyable()}); alle möglichen Überlastungen berücksichtigt werden. Aber nur der erste (der, der const value_type& nimmt) wird gefunden, da der andere keinen Sinn ergibt (es gibt keine Möglichkeit, auf magische Weise zu erraten, dass Sie ein Paar erschaffen wollten). Die erste über ­ Last funktioniert nicht natürlich, da Ihr Element nicht kopierbar ist.

Sie können die zweite Überlast Arbeit machen, indem das Paar explizit erstellen, entweder mit make_pair, wie Sie bereits beschrieben, oder durch den Werttyp Benennung ausdrücklich:

typedef std::map<int, non_copyable> map_type; 

map_type m; 
m.insert(map_type::value_type({1, non_copyable()})); 

Nun ist die Liste-initializer weiß zu suchen map_type::value_type Konstruktoren findet die relevante mova ­ BLE ein, und das Ergebnis ist ein R-Wert-Paar, das mit dem P&& -overload der insert Funktion bindet.

(Eine andere Möglichkeit ist emplace() mit piecewise_construct und forward_as_tuple zu verwenden, obwohl das viel ausführlicher bekommen würde.)

ich die moralische nehme hier ist, dass die Liste-Initialisierer für tragfähige Überlastungen aussehen – aber sie müssen wissen, Wonach schauen!

+1

Ja, das ist im Grunde, was ich geschrieben habe, bevor ich meine Antwort löschte. Ich habe allerdings einen Zweifel: Warum wird hier eine 'initializer_list <>' erstellt? 'std :: pair ' scheint keinen Konstruktor zu haben, der einen übernimmt. Ich dachte, die uniforme Initialisierungssyntax würde nur den regulären Konstruktor von 'pair <>' auswählen. –

+1

Auch initializer_list <> Elemente müssen alle vom selben Typ sein, nein? – eladidan

+0

+1 zu beiden Kommentaren. In meinem Beispiel sollte es nicht einmal eine 'initializer_list' geben. Es ist mehr wie ein Aufruf an 'std :: pair's Konstruktor, der eine einheitliche Initialisierung verwendet. – mfontanini