Ist C++ noch zu retten?



  • @5cript sagte in Ist C++ noch zu retten?:

    unnötiger overhead. Außerdem entsteht dann die Notwendigkeit zu wissen mit welchem Wert die Variable initialisiert wurde.

    Ah, also das was jetzt schon der Fall ist.

    Was für kern-bescheuerter Vorschlag wenn man argumentiert dass auto code obfuscated.
    EDIT: Du hast mein Beispiel schon wieder ignoriert.

    for (auto i = 0; i != 1000; ++i) // <- auto deduziert als unsigned char?
    

    Wo bitte ist denn der qualitative Unterschied zum status quo?

    for (auto i = 0; i != 10'000'000'000; ++i) {
    

    Das geht aktuell doch auch schon nicht. Der Punkt ist, dass an dieser Stelle auto Unfug ist.
    Schreibt man stattdessen

    for (auto i = 0, e = 10'000'000'000; i != e; ++i) {
    

    motzt der Compiler herum, dass die Deduktion des Typen nicht funktioniert, weil i und e unterschiedlich groß sein sollen. D.h. auch so löst man das Problem nicht. Man muss ohnehin explizit den Typ hinschreiben. Die beste Lösung ist es,

    for (size_t i = 0, e = 10'000'000'000; i != e; ++i) {
    

    zu schreiben bzw. den geeigneten Typ der Wahl. Das löst das Problem.

    Hier bitte auch:

    auto foo()
    {
        return 2;
    }
    
    int main()
    {
        auto bla = foo(); // fml
    }
    

    Warum sollte ich argumentieren, dass daran etwas gut sei? Ich bin derjenige, der sagt, dass auto schlecht ist. Aber wenn man sich damit auseinandersetzt, wo bitte ist denn der qualitative Unterschied zum status quo? Ob der Returntype von foo nun byte wäre oder int ist, macht doch qualitativ keinerlei Unterschied.

    Außerdem warum ziehst du dich an auto für Zahlen hoch?

    Weil das zu Problemen führt, wie Du ja gerade selbst zu erkennen beginnst.

    Auto für integrale datentypen interessiert doch kein mensch. Auto lohnt sich erst für komplexere Typen und rückgabewerte.

    Da spart es Code, aber zu einem Preis, dass verdeckt wird um welchen Typen es sich handelt. Es ist ja nicht so, dass man das Problem vor C++11 nicht gelöst hätte. auto erweitert die Sprache nicht um neue Konzepte, sondern erspart im besten Fall Schreibarbeit. Das Problem dabei ist, dass man i.d.R. Anforderungen an den gelieferten Typen stellt, d.h. man erwartet da als Rückgabewerte nicht irgend einen Typ sondern einen ganz konkreten. Man fängt hier also an mit impliziten Interfaces zu arbeiten und das widerspricht gerade dem Gedanken einer stark typisierten Programmiersprache. Wer SmallTalk will, soll's auch benutzen.

    Und jetzt erklär mir nochmal warum C++ mehr als ein Typsystem beinhaltet.

    Die Typbezeichner für die Literale und die PODs sind disjunkt. Man kann nicht ull a = 1'000'000'000 schreiben, sondern man muss unsigned long long oder auto auf der linken Seite verwenden, und umgekehrt kann man nicht auto a = unsigned long long 1'000'000'000 schreiben. D.h. Literale und PODs verwenden unterschiedlichen Typen, wobei man die Werte von Literale an korrespondierende POD Typen zuweisen kann.



  • @wob sagte in Ist C++ noch zu retten?:

    Allerdings finde ich die Regeln für auto logisch und den Unterschied zwischen auto und auto & oder bevorzugt const auto& absolut eindeutig. Ich mache auch sehr viel Python, wo es duck typing gibt. Auch oft kein Problem. Man braucht die exakten Typen nicht überall zu kennen.

    Genau darum geht es. C++ ist eine stark typisierte Sprache und eben keine schwachtypisierte Sprache wie etwa SmallTalk bei der man DuckTyping nutzt. Wenn man Interfaces nutzt, so soll das auch klar und deutlich dokumentiert sein und nicht implizit im Code verstreut sein. Jeder der länger C++ Templates nutzt kennt noch die absolut grausamen Fehlermeldungen, wenn man unpassende Typen an Templates übergab. Daraus erwuchs der Gedanke Konzepte in C++ einzuführen, weil implizite Interfaces zu total kryptisches Fehlermeldungen führte, da der Ort der Falschverwendung und der Ort der Fehlermeldung auseinander lagen bzw. sogar noch liegt.



  • @john-0 sagte in Ist C++ noch zu retten?:

    Genau darum geht es. C++ ist eine stark typisierte Sprache und eben keine schwachtypisierte Sprache

    Mir geht es aber darum, dass ich nicht an allen Stellen meines Codes die starken Typen brauche. Manchmal sind sie sinnvoll, dann nutzt man dort halt kein auto oder nur sporadisch auto. Manchmal aber machen starke Typen einem das Leben schwerer, ohne Mehrwert zu bieten - mehr noch, wenn man später man einen Typen in der Implementierung austauscht, muss man gleich überall Typen anpassen. Und wenn du jetzt argumentierst, dass Details nicht nach draußen leaken sollen, dann hast du generell zwar recht, aber oft lässt sich das nicht vermeiden oder es bietet Optimierungspotential oder die Implementierung wäre ungleich teurer (in Programmiererstunden).

    Für ein vollständiges Programm können in unterschiedlichen Stellen unterschiedliche Paradigmen sinnvoll sein.



  • Kleiner Hinweis: auto hat mit der Stärke des Typsystems nichts zu tun. Es ist ja sogar so, dass stark typisierte Sprachen oft mit sehr wenig expliziten Typangaben auskommen und der Rest inferiert wird.



  • @john-0 sagte in Ist C++ noch zu retten?:

    Wo bitte ist denn der qualitative Unterschied zum status quo?
    for (auto i = 0; i != 10'000'000'000; ++i) {

    Ja schreib ich jeden Tag sowas 🤣
    Nimm mal weniger drogen dude.



  • @5cript sagte in Ist C++ noch zu retten?:

    @john-0 sagte in Ist C++ noch zu retten?:

    Wo bitte ist denn der qualitative Unterschied zum status quo?
    for (auto i = 0; i != 10'000'000'000; ++i) {

    Ja schreib ich jeden Tag sowas 🤣

    Ich fand diesen Einwand von @john-0 durchaus berechtigt. Was ich schon sehr oft in Code gesehen habe, ist natürlich nicht die Loop von 1..10'000'000'000, sondern stattdessen:

    for (int i = 0; i != some_container.size(); ++i) { ... }
    
    // Oder dem folgenden, um die signed-Warnung zu ignorieren
    
    for (unsigned int i = 0; i != some_container.size(); ++i) { ... }
    

    Hältst du das für so abwegig?

    Nimm mal weniger drogen dude.

    Abstraktionsvermögen weg? Selber auf Droge? (sorry, wenn man anderen das unterstellt, unterstelle ich einfach mal zurück, auch wenn ich nicht angesprochen war)



  • Nein ist er nicht und zwar deswegen:
    warning: comparison of integer expressions of different signedness: 'int' and 'std::vector<char>::size_type' {aka 'long long unsigned int'} [-Wsign-compare]|
    und wenn man einen 2 gigabyte vector (char, das kleinste was geht) auf x86_32 hat oder einen vector größerer größe auf 64 bit dann ist man sich dessen bewusst. Das kommt nicht einfach so "ups, der ist aber groß"

    wer auf microcontrollern einen vector hat, dem gratulier ich.

    (oder andere datenstruktur)

    EDIT: Oder noch breiter: KEIN MENSCH iteriert ohne sich das bewusst zu sein über milliarden an elementen.



  • Mir ist klar, das der Thread eher theoretischer Natur ist, aber wie schafft man in x86 einen vector mit 10'000'000'000 Elementen? Ich bekomme bei der Erstellung einen bad_alloc Fehler. Unter x64 kompiliert klappt das mit der Anzahl. Und dann kann man auch mit auto iterieren.

    int main()
    {
    	std::vector<char> vec(10'000'000'000);
    	std::cout << vec.size();
    
    	for (auto i = 0; i != vec.size(); ++i) {}
    	std::cout << "\nfertig";
    }
    


  • Gar nicht.
    Du kriegst das nie im leben als kontinuierlichen Block, außer vllt auf einem speziellen System?
    Es kommt drauf an wie viel RAM du hast und wie fragmentiert der ist.
    Und wegen addressing ist es sogar unmöglich in 32 bit.



  • Aber das ist dann doch kein Problem von auto, sondern wie man kompiliert?

    Ich versuche gerade

    for (auto i = 0; i != 10'000'000'000; ++i) {}
    

    aber da rechnet er schon seit über 10 Minuten.



  • Das muss ja kein Container sein, das könnte z.B. sowas wie ein Generator sein. Darum ging es jetzt glaub ich nicht primär.
    Aber so oder so, ich halte diese Diskussion für nicht relevant. auto für Zahlen finde ich auch völlig überflüssig, ändert aber nichts daran, dass auto ein sehr großer Gewinn für die Sprache war.



  • @zeropage sagte in Ist C++ noch zu retten?:

    aber da rechnet er schon seit über 10 Minuten.

    Ja genau. Mein reden.



  • Dieses auto- Beispiel war das einzige, was ich ohne größere Überlegungen nachtippen konnte. Und wenn das aber geht, dann verstehe ich eine auto-Problematik erst recht nicht.
    Wenn es mit autonicht geht, würde es mit jedem anderen Standard-Typen auch nicht gehen. Das hat doch dann nichts mit autozu tun?



  • Er hat gemeint es wäre besser wenn auto statt int bei auto i = 0 lieber den kleinst möglichen typ vom Wert ermitteln sollte, also hier unsigned char (oder char?, char wär falsch).

    wohingegen
    for (auto i = 0; i != 365; ++i) noch ein realistisches beispiel ist was man, oder ein anfänger schnell mal reinzimmert und sich dann wundert warum das ne endlosschleife ist, dachte er er könnte countern und sagen:
    "wieso? das hier geht doch jetzt auch schon nicht?"
    for (auto i = 0; i != SOMETHING_LUDICROUS; ++i)
    Dadurch enstand das erst.



  • Ok, danke 😉



  • @zeropage sagte in Ist C++ noch zu retten?:

    Unter x64 kompiliert klappt das mit der Anzahl. Und dann kann man auch mit auto iterieren.

    Kann man nicht, jedenfalls nicht so wie du. Denn deine Schleifenvariable ist ein int, kann also die Größe des Vektors gar nicht ausdrücken, jedenfalls nicht auf x64. Du bekommst irgendwann einen Überlauf. Das ist sogar undefiniertes Verhalten. Im günstigsten Fall ist es ein Überlauf, dein i kippt also bei 2,x Milliarden nach -2,x Milliarden über und wird niemals gleich 10 Milliarden sein, also hast du eine Endlosschleife.

    Du hast damit das Problem mit auto vorzüglich illustriert. Sogar ein Upvote dafür bekommen!



  • Oho, danke. Wenn auch unverdient. Mit size_t geht es dann aber. Wenn ich dort stattdessen auto einsetze, kommt keine entsprechende Ausgabe, wird dann nicht soweit gekommen sein. Habe ich mich von der "fertig"-Meldung blenden lassen.

    int main()
    {
    	std::vector<char> vec(10'000'000'000);
    	std::cout << vec.size();
    
    	std::size_t n = 0;
    	for (std::size_t i = 0; i != vec.size(); ++i)
    	{
    		++n;
    		if (n == 3'000'000'000)
    			std::cout << "\n3 Milliarden: " << n;
    	}
    	std::cout << "\nfertig";
    }
    


  • @Bashar sagte in Ist C++ noch zu retten?:

    Kann man nicht, jedenfalls nicht so wie du. Denn deine Schleifenvariable ist ein int, kann also die Größe des Vektors gar nicht ausdrücken, jedenfalls nicht auf x64. Du bekommst irgendwann einen Überlauf. Das ist sogar undefiniertes Verhalten. Im günstigsten Fall ist es ein Überlauf, dein i kippt also bei 2,x Milliarden nach -2,x Milliarden über und wird niemals gleich 10 Milliarden sein, also hast du eine Endlosschleife.

    Korrekt, es ist UB, aber gerade weil es UB ist kann es sein dass es trotzdem funktioniert. Grund: weil signed Integer Overflow UB ist, kann der Compiler davon ausgehen dass es keinen Overflow gibt. Er kann also das 64 Bit Register mit 0 initialisieren und dann einfach hochzählen. Und sich darauf verlassen dass das Bits 63 ... 31 immer 0 bleiben.

    Beispiel: https://godbolt.org/z/5Eh4er

    #include <stddef.h>
    
    void foo(unsigned char* data, size_t size) {
        for (int i = 0; i < size; i++)
            data[i] = i;
    }
    

    Compiliert GCC 10 zu

    foo(unsigned char*, unsigned long):
            test    rsi, rsi
            je      .L1
            xor     eax, eax
    .L3:
            mov     BYTE PTR [rdi+rax], al
            add     rax, 1
            cmp     rax, rsi
            jne     .L3
    .L1:
            ret
    

    Wie man sieht wird für den Schleifenzähler ein 64 Bit Register verwendet und dieses wird direkt mit der 64 Bit size Variable verglichen.

    Ändert man dagegen den Typ von i zu unsigned int, bekommt man

    foo(unsigned char*, unsigned long):
            test    rsi, rsi
            je      .L1
            xor     eax, eax
            xor     edx, edx
    .L3:
            mov     BYTE PTR [rdi+rdx], al
            lea     edx, [rax+1]
            mov     rax, rdx
            cmp     rdx, rsi
            jb      .L3
    .L1:
            ret
    

    Hier stellt GCC also sicher dass der Wert in rdx niemals >= 2^32 wird.

    Der Aufruf der Funktion mit size >= 2^32 ist nebenbei erwähnt trotzdem UB, da es auch eine Regel gibt die besagt dass ein Programm kontinuierlich beobachtbare Effekte zu produzieren hat. Bzw. anders gesagt: es darf nicht endlos in einem Loop festhängen der keine beobachtbaren Effekte hat. Und das Lesen oder Schreiben von Speicher zählt ohne volatile oder atomic Operations nicht als beobachtbarer Effekt.

    D.h. GCC dürfte sich genaugenommen selbst hier darauf verlassen dass es keinen Overflow gibt. Tut er in diesem Fall bloss nicht.

    Und zu guter letzt: Eine Schleife wie for (auto i = 0; i != vec.size(); ++i) {} kann der Compiler natürlich sowieso gleich ganz wegoptimieren - die tut ja schliesslich nichts.



  • @5cript sagte in Ist C++ noch zu retten?:

    Gar nicht.
    Du kriegst das nie im leben als kontinuierlichen Block, außer vllt auf einem speziellen System?
    Es kommt drauf an wie viel RAM du hast und wie fragmentiert der ist.

    Mein aktuelles System hat 32 GB RAM. Da bekomme ich locker einen zusammenhängenden Block mit 10 GB. Fragmentierung spielt da auch keine Rolle, weil 64 Bit Adressraum und virtuelle Adressen. D.h. das OS mappt einfach die einzelnen Pages so in den 64 Bit Adressraum des Prozesses dass sich dort ein zusammenhängender Block ergibt. Wo die Pages physikalisch liegen spielt dabei überhaupt keine Rolle.

    Und wegen addressing ist es sogar unmöglich in 32 bit.

    Ja, klar. Doh 🙂



  • @5cript sagte in Ist C++ noch zu retten?:

    EDIT: Oder noch breiter: KEIN MENSCH iteriert ohne sich das bewusst zu sein über milliarden an elementen.

    Da bin ich anderer Meinung. Es gibt genug Leute die Code schreiben der allgemein verwendbar sein sollte, und sich über sowas keine Gedanken machen. Und es gibt leider auch genug Leute denen nicht klar ist, dass man Code der für ein bestimmtes Projekt entwickelt wurde, nicht einfach so da rausnehmen und als allgemeingültig erklären sollte, ohne ihn davor nochmal komplett zu reviewen und dann entsprechend zu überarbeiten.

    Also grundsätzlich finde ich das Beispiel schon valide. Ich bin aber nicht der Meinung dass man auto deswegn als Fehldesign oder sonstwie kaputt ansehen sollte. Denn wie schon gesagt: wer Dinge verwendet die er nicht versteht, wird früher oder später mit allem Mist bauen. Und es ist ja nicht so dass die Regeln für auto besonders kompliziert wären. Ganz im Gegenteil: jeder Versuch auto irgendwie sicherer zu machen würde die Regeln wohl deutlich verkomplizieren.


Anmelden zum Antworten