ref-qualified begin/end bei STL-Containern



  • Hallo

    Wieso bieten die STL-Container keine &&-qualifizierten Überladungen für {r}begin/{r}end an, die Move-Iteratoren zurückgeben? Übersehe ich gerade etwas Offensichtliches oder wäre es in der Tat am besten, würde std::move(vec).begin() einen Move-Iterator zurückgeben?

    LG



  • Was soll ein Move-Iterator sein?



  • manni66 schrieb:

    Was soll ein Move-Iterator sein?

    Schon einmal 'was von Google gehört? http://en.cppreference.com/w/cpp/iterator/move_iterator



  • Und was spricht dann dagegen?

    std::vector<Foo> foos;
    auto itr = std::make_move_iterator(std::begin(foos));
    

    EDIT:
    Ich glaub ich verstehe jetzt was du meinst. Wenn du allerdings nur eine rvalue-ref auf einen vector hast, und am Ende nur die Iteratoren übrig bleiben, hast du undefined behaviour.



  • @Fytch
    Meinst du für Verwendung in Funktionen die Universal-Ref Parameter als Input nehmen? Da ist mir nämlich auch schon aufgefallen dass es da keine (einfache, vorgefertigte) Möglichkeit gibt die Rvalueness eines Container-Arguments auf die im Container gespeicherten Items zu übertragen.

    Im Endeffekt hab ich mir dann eine "forward_container_element<T>" Hilfsfunktion geschrieben.

    Die Overloads für begin/end die du beschreibst wären vermutlich auch ne gute Idee.



  • @DNKpp
    Denk an sowas

    template <class T>
    void foo(T&& container) {
        std::copy(
            std::forward<T>(container).begin(),
            std::forward<T>(container).end(),
            somewhere);
    }
    

    Sieht nett und "move-ig" aus, wird aber nicht moven so lange begin/end keine entsprechenden Overloads haben.



  • DNKpp schrieb:

    Wenn du allerdings nur eine rvalue-ref auf einen vector hast, und am Ende nur die Iteratoren übrig bleiben, hast du undefined behaviour.

    Ja, das gleiche Problem hat man aber auch mit herkömmlichen Iteratoren und einer lvalue-Referenz auf den Container. Bleiben nur Iteratoren auf den Container, nicht aber der Container selbst übrig, und benutzt man die Iteratoren, so hat man stets UB. Insofern sind Move-Iteratoren nicht anfälliger auf UB als normale Iteratoren.

    hustbaer schrieb:

    Meinst du für Verwendung in Funktionen die Universal-Ref Parameter als Input nehmen? Da ist mir nämlich auch schon aufgefallen dass es da keine (einfache, vorgefertigte) Möglichkeit gibt die Rvalueness eines Container-Arguments auf die im Container gespeicherten Items zu übertragen.

    Im Endeffekt hab ich mir dann eine "forward_container_element<T>" Hilfsfunktion geschrieben.

    Die Overloads für begin/end die du beschreibst wären vermutlich auch ne gute Idee.

    Ja, genau in diesem Kontext habe ich mir solche Funktionen gewünscht. In gewissen Spezialfällen kann man das Problem umgehen, z.B. wenn man weiß, dass das Argument einen bestimmten Containertyp hat und man den ganzen Container konsumieren möchte, denn dann macht man lokal eine Instanz dieses Containertyps und forwarded das Funktionsargument dort hinein. Aber wenn ein beliebieger Containertyp möglich ist oder wenn nur eine Teilrange konsumiert werden soll, dann geht das nicht mehr.

    Eine forward_container_element<T> -Funktion wäre das operator[] -Pendant meiner Idee, wenn ich das richtig verstanden habe. Ja, das sollte man dann konsequenterweise ebenfalls implementieren, gleichfalls mit at , front und back .

    Vielleicht könnte man sogar noch einen Schritt weiter gehen und sagen, dass die &&-qualifizierten Overloads der Funktionen {r}begin und {r}end in std::set und ähnlichen Containern non-const Move-Iteratoren zurückgeben sollen (statt den üblichen Const-Iteratoren), damit man z.B. ein Set effizient in einen Vektor transferieren kann. Natürlich sagt man dann, das Set ist in einem indeterminierten Zustand und alle längenveränderenden Operationen (außer clear und 🙂 sowie alle Suchfunktionen erzeugen UB. Aber vielleicht ist das ein unsinniges Szenario und man möchte in der Realität gar nie einen assoziativen Container irgendwohin transferieren. 🙂



  • Fytch schrieb:

    Natürlich sagt man dann, das Set ist in einem indeterminierten Zustand und alle längenveränderenden Operationen (außer clear und 🙂 sowie alle Suchfunktionen erzeugen UB. Aber vielleicht ist das ein unsinniges Szenario und man möchte in der Realität gar nie einen assoziativen Container irgendwohin transferieren. 🙂

    Ich weiss nicht ob es ein unsinniges Szenario ist. Ich weiss nur dass ich es immer ganz ganz schlecht finde wenn man Objekte in einen Zustand bringen kann wo das "normale Weiterverwenden" des Objekts zu UB führen kann. Weil es u.A. die Fehlersuche enorm erschwert.

    Denn sobald du das erlaubst wird es viel schwieriger Fehler zu suchen. Weil man bei z.B. Crashes oder auch sonstigem unerwarteten Verhalten oft nicht mehr einfach sagen kann ob es ein Fehler in der Klasse oder ein Anwendungsfehler ausserhalb der Klasse ist.

    Fytch schrieb:

    Ja, genau in diesem Kontext habe ich mir solche Funktionen gewünscht. In gewissen Spezialfällen kann man das Problem umgehen, z.B. wenn man weiß, dass das Argument einen bestimmten Containertyp hat und man den ganzen Container konsumieren möchte, denn dann macht man lokal eine Instanz dieses Containertyps und forwarded das Funktionsargument dort hinein. Aber wenn ein beliebieger Containertyp möglich ist oder wenn nur eine Teilrange konsumiert werden soll, dann geht das nicht mehr.

    Naja, du könntest dir eine forward_container_iterator Hilfsfunktion basteln.
    Die dann so zu verwenden wäre:

    template <class T>
    void foo(T&& container) {
        std::copy(
            std::forward_container_iterator<T>(container.begin()),
            std::forward_container_iterator<T>(container.end()),
            somewhere);
    }
    


  • Also für begin/end fände ich das nicht so schön, weil dann zwei mal move oder forward auf dasselbe Container-Objekt angewendet wird. Das widerstrebt mir.

    Für "Ranges" kann ich es mir schon eher vorstellen. Vorhin ist mir das std::move bei einem range-v3 Beispiel aufgefallen:

    vector<int> vi = ...;
    vi = std::move(vi) | action::sort | action::unique;
    

    siehe
    https://ericniebler.github.io/range-v3/index.html#tutorial-quick-start



  • krümelkacker schrieb:

    Also für begin/end fände ich das nicht so schön, weil dann zwei mal move oder forward auf dasselbe Container-Objekt angewendet wird. Das widerstrebt mir.

    So lässt es sich halt ohne Änderung am Standard dazuprogrammieren.
    Man kann natürlich auch 5 Jahre warten falls man die Zeit hat.


Anmelden zum Antworten