std::size (analog zu begin/end)



  • Wie wäre es mit std::size ?

    Das wäre doch mal was. Auch eine allgemeinere Form von .size() für Container oder sizeof für Arrays.

    Sollte doch eigentlich auch dabei sein. (Oder ich übersehe gerade etwas*; die Idee ist mir vor etwa einer halben Minute in den Kopf gekommen)

    ~* Ja, ich glaube das braucht einfach kein Schwein...~



  • Bist nicht der einzige, der das braucht, ein size für Arrays hat wohl jeder C++-Programmierer schon seit jeher in seinem Code.

    Nur für generischen Code bringt es halt nichts, denn was will man damit schon anstellen? Fehlt noch ein nonmember-at(), dann hätten wir eine schlechte Kopie von RAIters.


  • Mod

    Die übliche Schreibweise ist eben mit begin/end, was die gleiche Funktion erfüllt. Oder was würdest du sonst mit der size so machen, was häufig vorkäme?



  • Ehrlich gesagt find ich auch std::begin()/end() unnötig.
    Das Argument war, die begin/end von built-in Arrays zu erhalten.
    Und im gleichen Standard kam dann std::array... 🙄

    Edit: OK, man könnte inkompatible Container kompatibel machen, aber ich kenne keine Library mit Containern, die nicht eh schon mit der STL kompatibel ist.


  • Mod

    std::begin/end wurde eingeführt, im den range-for-loop zu ermöglichen.



  • schwein schrieb:

    ein size für Arrays hat wohl jeder C++-Programmierer schon seit jeher in seinem Code

    Nein. Erst recht nicht, seit ich keine Arrays mehr benutze.

    Ich kann mir gerade keine Situation vorstellen, wo ein globales size() wirklich Vorteile brächte.



  • Ich sehe schon, das wäre völliger Blödsinn.

    Aber wenn wir schon dabei sind, ich habe noch mehr verrückte Ideen: Ich wünsche mir ein Sprachfeature, dass einem Ausdruck erlaubt, in mehrere (durch Kommas getrennte) Ausdrücke zu zerfallen. Beispiel:

    class vector
    {
        operator list_proxy<iterator, iterator>() { return {begin(), end()}; }
    };
    

    list_proxy selbst zerfällt dann (durch Compiler-Hokuspokus) in eine syntaktische Liste aller Bestandteile.

    std::copy( vec, iter );
    // =>
    std::copy( std::get<0>(list_proxy<iterator, iterator>::tuple()), std::get<1>(list_proxy<iterator, iterator>::tuple()), iter );
    

    Alter Code muss nicht verändert werden; Overload-Resolution lässt alle anderen Fälle besser aussehen.

    Nicht böse sein, bin angetrunken.



  • camper schrieb:

    std::begin/end wurde eingeführt, im den range-for-loop zu ermöglichen.

    Aber warum sagen dann manche, man solle bei den Funktionen die globalen statt der Methoden verwenden, also

    std::sort(std::begin(container), std::end(container));
    

    anstatt

    std::sort(container.begin(), container.end());
    

    und begründen, dass man damit auch zu built-in Arrays konsistent wäre, man gleichzeitig aber keine Builtin-Arrays sondern std::array verwenden soll.

    Sone schrieb:

    Ich wünsche mir ein Sprachfeature, dass einem Ausdruck erlaubt, in mehrere (durch Kommas getrennte) Ausdrücke zu zerfallen. Beispiel:

    class vector
    {
        operator list_proxy<iterator, iterator>() { return {begin(), end()}; }
    };
    

    list_proxy selbst zerfällt dann (durch Compiler-Hokuspokus) in eine syntaktische Liste aller Bestandteile.

    std::copy( vec, iter );
    // =>
    std::copy( std::get<0>(list_proxy<iterator, iterator>::tuple()), std::get<1>(list_proxy<iterator, iterator>::tuple()), iter );
    

    Hier würde ich mir eine Art range-class wünschen, also sowas wie

    std::sort(container.range());
    


  • Marthog schrieb:

    man gleichzeitig aber keine Builtin-Arrays sondern std::array verwenden soll.

    Man soll nicht einfach std::array nehmen.

    Normale Arrays haben alle Vorzüge von std::array, dafür aber noch eine nettere Syntax und sie kopieren sich nicht einfach so (weil das teuer ist und besser vermieden wird).

    Wer Kopien explizit will, und nur dann, soll std::array verwenden. std::expensive_copyable_array wäre ein besserer Name gewesen.



  • anti-stdarray-fraktion schrieb:

    Normale Arrays haben alle Vorzüge von std::array, dafür aber noch eine nettere Syntax und sie kopieren sich nicht einfach so (weil das teuer ist und besser vermieden wird).

    Ich behaupte das Gegenteil:
    std::array ist schöner, weil man ein standard-container-interface hat, man mit einem einfachen und übersichtlichen Operator kopieren oder Vergleichen kann, man zusätzlich noch praktische Funktionen (fill, swap, reverse-iteratoren, bound checks) hat und gleichzeitig alle Vorteile von Builtin-arrays, außer mehrdimensionalen arrays und automatischem herausfinden der Größe hat, dieses aber sehr einfach nachrüsten könnte.



  • anti-stdarray-fraktion schrieb:

    Normale Arrays haben alle Vorzüge von std::array

    Ähm, nein. Du hast mit rohen Arrays keine Wertsemantik, keine Möglichkeit zur Übergabe und Rückgabe von Funktionen, kein STL-Interface, keine Debug-Checks und weniger Typsicherheit durch array-to-pointer decay.

    Die Syntax ist nur gerade bei der Deklaration netter. Sobald du kopieren musst, kannst du ziemlich viel Boilerplate-Code schreiben.



  • std::size und ähnliches wäre sinnvoll, wenn es range Adaptoren a la boost::range gäbe. Da kann man das gut gebrauchen.



  • Könnte man nicht ein generisches std::size() über

    template <typename T>
    size_t size(const T& container)
    {
        return distance(begin(container), end(container));
    }
    

    implementieren (evtl. auch für die Fanatiker mit generischem Rückgabetyp)? Ist dann je nach Kategorie halt langsam; bei Typen wie std::list könnte man als Optimierung Überladungen anbieten. Bei std::forward_list wirds schon schwieriger...

    Aber wie oft braucht ihr denn size() , gerade bei Containern ohne Random-Access-Iteratoren? Bei Ranges habe ich das Gefühl, ein empty() ist viel wichtiger...



  • @Nexus: Das ist doch Blödsinn. Man macht das per SFINAE, wie auch sonst; alle Typen, die eine Funktion size() haben, bei denen wird das auch aufgerufen.

    Für alle Typen, die eine Funktion begin/end aber keine Funktion size() haben, kommt deine Variante ins Spiel. Und sonst geht es um Überladungen.

    Aber wie oft braucht ihr denn size(), gerade bei Containern ohne Random-Access-Iteratoren? Bei Ranges habe ich das Gefühl, ein empty() ist viel wichtiger...

    Mir ging es darum, einige Operationen die nicht von der Gesamtfunktionalität der Klasse abhängen zu entkoppeln und generisch zu machen. Das gilt sowohl für empty(), size() oder auch Typedefs wie value_type (wobei bei letzterem wohl keine schöne direkte Schreibweise wie ein Funktionsaufruf möglich ist*).

    * Am ehesten etwas wie decltype(elem_t(vec)) , wobei aber schon das recht hässlich ist.



  • Sone schrieb:

    @Nexus: Das ist doch Blödsinn. Man macht das per SFINAE

    Meinst du nicht ADL?



  • otze schrieb:

    Sone schrieb:

    @Nexus: Das ist doch Blödsinn. Man macht das per SFINAE

    Meinst du nicht ADL?

    ADL und SFINAE 🙂



  • Python hat auch len() als freie, builtin Funktion. 😉



  • Sone schrieb:

    otze schrieb:

    Sone schrieb:

    @Nexus: Das ist doch Blödsinn. Man macht das per SFINAE

    Meinst du nicht ADL?

    ADL und SFINAE 🙂

    Der "member function detection" Trick gehört mit zum kranksten was C++ zu bieten hat. Sowas mag dir gefallen, aber in Production-Code hat das mMn. nix verloren. Also nein, sowas macht "man" nicht so.

    Und es spricht mMn. auch nichts gegen Overloads + ADL für die paar Klassen die ein spezielles size brauchen.
    Speziell nicht wenn man als Default-Implementierung nicht das u.U. langsame return distance(begin(x), end(x)) macht, sondern einfach return x.size() .



  • hustbaer schrieb:

    Der "member function detection" Trick gehört mit zum kranksten was C++ zu bieten hat. Sowas mag dir gefallen, aber in Production-Code hat das mMn. nix verloren. Also nein, sowas macht "man" nicht so.

    Ich weiß jetzt nicht, was für einen Trick du meinst, aber sowas hier

    template<class T>
    auto size(T&& o) -> decltype(o.size())
    { return o.size(); }
    

    wird dank SFINAE einfach bei Typen T, die kein .size() anbieten, ignoriert, weil da der Rückgabetyp nicht deduziert werden kann. Man muss, wenn man mehrere Overloads anbieten will, die Auflösung nur etwas steuern, sonst gibt's Mehrdeutigkeiten. Also, wenn wir das hier als Überladung hinzufügen

    template<class T>
    auto size(T&& o) -> decltype(std::distance(o.begin(),o.end()))
    { return std::distance(o.begin(),o.end()); }
    

    dann hätten wir für T=vector<int> eine Mehrdeutigkeit. Kein Template ist besser als das andere. Da hilft aber Tag-Dispatching mit Partial Ordering:

    namespace size_impl {
    
      using std::begin;
      using std::end;
      using std::distance;
    
      struct fallback {};
      struct preferred : fallback {};
    
      template<class T>
      auto size(T&& o, preferred)
      -> decltype(o.size())
      { return o.size(); }
    
      template<class T>
      auto size(T&& o, fallback)
      -> decltype(distance(begin(o),end(o)))
      { return distance(begin(o),end(o)); }
    
    } // namespace size_impl
    
    template<class T>
    auto size(T&& o)
    -> decltype(size(o,size_impl::preferred()))
    { return size(o,size_impl::preferred()); }
    

    (ungetestet)

    Da wünscht man sich eigentlich nur noch, dass man das "-> decltype(...)" nicht mehr schreiben muss. 🙂



  • Da wünscht man sich eigentlich nur noch, dass man das "-> decltype(...)" nicht mehr schreiben muss. 🙂

    Genau dafür hast du doch eigens ein Makro definiert ( AUTO_RETURN ).

    Sollte nicht übrigens der Parameter der beiden Hilfsfunktionen eine const -Referenz sein?

    Der "member function detection" Trick gehört mit zum kranksten was C++ zu bieten hat.

    Finde ich nicht.

    Sowas mag dir gefallen, aber in Production-Code hat das mMn. nix verloren.

    Pardon? Production Code?


Anmelden zum Antworten