Warum gibt's sowas eigentlich nicht als Feature in C++?



  • Ich weiß, das ist vielleicht "gefährlich", aber man könnte so etwas ja optional mit detaillierten Ausgaben versehen. Mal 2 Beispiele, was ich mir vorstellen könnte:

    //für so etwas würde man normalerweise einen manuellen Schalter benötigen, der höchstens per Configure-Script selbst gesetzt werden könnte,
    //es gibt nämlich keine #define-Konstante für C++0x oder den TR1 
    #TRY CODE START  //es soll ausprobiert werden, ob dieser Code hier kompiliert, ansonsten wird die nächste Möglichkeit ausprobiert usw.
    #include <unordered_map>   //unterstützt der Compiler kein C++0x, schlägt das Include fehl
    #define STRMAP std::unordered_map
    #TRY CODE ELSE
    #include <tr1/unordered_map>  //wenn der Compiler kein C++0x unterstützt, kann es trotzdem sein, dass der TR1 unterstützt wird.
    #define STRMAP std::tr1::unordered_map
    #TRY CODE ELSE
    #include <map>
    #define STRMAP std::map   //und sonst müssen wir halt die langsame Variante benutzen
    #TRY CODE END
    

    oder

    //das hier wäre jetzt, um ein default-initialisiertes wxWidgets-Objekt zu erstellen (manche benötigen noch einen String im Konstruktor wie Buttons, andere nicht)
    template<typename T> void ConstructwxObject(T *obj)
    {
        using try start    //jaja, bloß keine neuen Keywords einführen^^
        {
            obj = new T(NULL, wxID_ANY, wxEmptyString);
        }
        using try else
        {
            obj = new T(NULL, wxID_ANY);
        }
        using try else
        {
            obj = new T();
        }
    }
    


  • Die Idee bekämpft Symptome und keine Ursachen, deshalb ist sie quasi nutzlos.

    Viel besser ist da einfach perfect forwarding.

    Und ob eine include Datei existiert oder nicht sagt nicht viel aus. Besser ist auf die defines zu achten die die Compiler anbieten um zu erkennen ob es tr1 oder boost oder dergleichen gibt. bzw ein Makro anzubieten mit dem der User definieren kann was er gerne verwenden möchte. Kann ja sein dass es zwar tr1/unordered_map gibt, ich aber lieber boost verwenden will weil das besser implementiert ist.



  • Shade Of Mine schrieb:

    Die Idee bekämpft Symptome und keine Ursachen, deshalb ist sie quasi nutzlos.

    Viel besser ist da einfach perfect forwarding.

    Und ob eine include Datei existiert oder nicht sagt nicht viel aus. Besser ist auf die defines zu achten die die Compiler anbieten um zu erkennen ob es tr1 oder boost oder dergleichen gibt. bzw ein Makro anzubieten mit dem der User definieren kann was er gerne verwenden möchte. Kann ja sein dass es zwar tr1/unordered_map gibt, ich aber lieber boost verwenden will weil das besser implementiert ist.

    Ich weiß nicht, ob du mich falsch verstanden hast, ich meinte, dass man so etwas selbst implementieren könnte, denn es gibt ja anscheinen keinen Schalter für den TR1. Ich meinte _nicht_, dass diese 2 Sachen einfach im Compiler oder einer Standardlibrary implementiert wären.



  • Wie willst du Try&Error-Kompilieren selbst implementieren? Mehr als SFINAE wirst du nicht hinkriegen.

    Zu deinem ConstructwxObject : Das kannst du mit Template-Spezialisierung je nach Typ lösen. Wenn du mehrere Typen zusammenfassen willst, helfen dir Metaprogrammierungs-Tricks.



  • Dann verstehe ich dich wirklich nicht.
    Wenn man wissen will ob etwas kompiliert oder nicht bevor man den echten Code kompiliert kann man ja das Build Script dazu verwenden es erstmal zu testen und dann die jeweiligen defines zu setzen.

    Natürlich könnte man es auch in der Sprache selber definieren dass soetwas möglich ist - aber der große Vorteil fehlt. Denn das was du willst kannst du jetzt schon machen. Nur eben nicht mit C++ Code sondern mit deinem Build Environment.



  • Nexus schrieb:

    Wie willst du Try&Error-Kompilieren selbst implementieren? Mehr als SFINAE wirst du nicht hinkriegen.

    Zu deinem ConstructwxObject : Das kannst du mit Template-Spezialisierung je nach Typ lösen. Wenn du mehrere Typen zusammenfassen willst, helfen dir Metaprogrammierungs-Tricks.

    Zu 1.: Ich will, dass mir der Compiler erlaubt/die Möglichkeit gibt, das selbst zu implementieren!
    2. Hm... Beim "mehrere Typen zusammenfassen" weiß ich jetzt nicht wie das geht, aufwändiger wäre es aber wahrscheinlich trotzdem. Außerdem müsste man so eine Implementierung nicht für neuere Versionen mit mehr Typen warten.



  • Shade Of Mine schrieb:

    Dann verstehe ich dich wirklich nicht.
    Wenn man wissen will ob etwas kompiliert oder nicht bevor man den echten Code kompiliert kann man ja das Build Script dazu verwenden es erstmal zu testen und dann die jeweiligen defines zu setzen.

    Natürlich könnte man es auch in der Sprache selber definieren dass soetwas möglich ist - aber der große Vorteil fehlt. Denn das was du willst kannst du jetzt schon machen. Nur eben nicht mit C++ Code sondern mit deinem Build Environment.

    Gut, da gebe ich dir recht, die 1. Variante könnte man über ein Build Script implementieren, auch wenn es aufwändiger wäre und man dann Code::Blocks auf Makefiles umstellen müsste.
    Bei der 2. Variante allerdings müsste der Compiler je nach Templateinstantiierung unterscheiden und das kriegt man so nicht per Makefile hin...



  • @wxSkip: Du hast einfach nur irgendwelchen Quelltexttext hingekackt. Kannst du dein Problem mal ordentlich formulieren? Auch verstehe ich nicht, warum du std::map und std::unordered_map in einen Topf werfen moechtest, da ganz andere Voraussetzungen fuer die entsprechenden Elemente gelten.



  • knivil schrieb:

    @wxSkip: Du hast einfach nur irgendwelchen Quelltexttext hingekackt. Kannst du dein Problem mal ordentlich formulieren? Auch verstehe ich nicht, warum du std::map und std::unordered_map in einen Topf werfen moechtest, da ganz andere Voraussetzungen fuer die entsprechenden Elemente gelten.

    OK, kann ich gerne machen. Ich habe mir ein Mittel vorgestellt, mit dem man Code verwenden kann, sofern er funktioniert, und alternativ anderen Code. Die Syntax dafür könnte auch ganz anders aussehen. Das waren jetzt Beispiele, wie ich so etwas verwenden würde.
    Das mit den maps habe ich so gedacht, dass man als "Standardlösung" eine map verwendet, und wenn eine unordered_map zur Verfügung steht, diese Hash-Map als effizientere Lösung für maps mit strings als Keys verwenden kann (für die anderen maps kann man natürlich auch weiterhin die std::map verwenden). Natürlich könnte man jetzt sagen, ich könnte mir gleich eine eigene Hashmap schreiben oder boost verwenden, aber ich meinte das jetzt als Möglichkeit für reine STL-Programme. Man muss da natürlich außerdem noch beachten, dass man beim Iterieren nicht mehr sichergehen kann, dass die Keys in alphabetischer Reihenfolge kommen.



  • wxSkip schrieb:

    OK, kann ich gerne machen. Ich habe mir ein Mittel vorgestellt, mit dem man Code verwenden kann, sofern er funktioniert, und alternativ anderen Code. Die Syntax dafür könnte auch ganz anders aussehen. Das waren jetzt Beispiele, wie ich so etwas verwenden würde.

    Und diese Idee ist ansich nur eine Bekämpfung von Symptomen. Warum willst du unterschiedliche map Implementierungen verwenden - doch nur weil deine bevorzugte nicht da ist. Warum unterschiedliche Ctor Aufrufe - doch nur weil die bevorzugte Variante nicht geht.

    Das ist ein typischer Fall für introspection/reflection.

    Jedes deiner Beispiele lässt sich auch anders lösen - nur wäre Reflection hier sehr nett. Code testweise kompilieren ist nicht unproblematisch - du würdest dir mit deiner Idee eine Menge Probleme einkaufen.

    Es ist besser das Problem ansich anzupacken als die Symptome zu bekämpfen. Du weißt nicht ob es unorderd_map gibt -> also teste es vorher. Ideal im Build Script lösbar.

    Du willst einen Ctor aufrufen den du nicht genau kennst: perfect forwarding.
    Du willst wissen ob eine Funktion so aufrufbar ist: SFINAE

    Ein Trial&Error kompilieren ist einfach eine furchtbar schlechte Idee.



  • Shade Of Mine schrieb:

    wxSkip schrieb:

    OK, kann ich gerne machen. Ich habe mir ein Mittel vorgestellt, mit dem man Code verwenden kann, sofern er funktioniert, und alternativ anderen Code. Die Syntax dafür könnte auch ganz anders aussehen. Das waren jetzt Beispiele, wie ich so etwas verwenden würde.

    Und diese Idee ist ansich nur eine Bekämpfung von Symptomen. Warum willst du unterschiedliche map Implementierungen verwenden - doch nur weil deine bevorzugte nicht da ist. Warum unterschiedliche Ctor Aufrufe - doch nur weil die bevorzugte Variante nicht geht.

    Das ist ein typischer Fall für introspection/reflection.

    Jedes deiner Beispiele lässt sich auch anders lösen - nur wäre Reflection hier sehr nett. Code testweise kompilieren ist nicht unproblematisch - du würdest dir mit deiner Idee eine Menge Probleme einkaufen.

    Es ist besser das Problem ansich anzupacken als die Symptome zu bekämpfen. Du weißt nicht ob es unorderd_map gibt -> also teste es vorher. Ideal im Build Script lösbar.

    Du willst einen Ctor aufrufen den du nicht genau kennst: perfect forwarding.
    Du willst wissen ob eine Funktion so aufrufbar ist: SFINAE

    Ein Trial&Error kompilieren ist einfach eine furchtbar schlechte Idee.

    Das mit den Problemen ist für mich verständlich. Ich hätte da aber noch ein paar Verständnisfragen:
    1. Unter perfect forwarding habe ich eigentlich verstanden, dass ich rvalues, die an eine Funktion übergeben werden, effizienter oder ohne Geschwindigkeitsverlust durch Rvalue-Referenzen an einen Konstruktor/eine Funktion weitergeben kann. Könntest du da bitte mal ein Beispiel machen, wie du das meinst?

    2. Okay, SFINAE kannte ich so noch nicht. Würde ich das für Konstruktoren verwenden, müsste ich doch alle wxWidgets-Klassen modifizieren, oder? Außerdem (aus Interesse), was würde passieren, wenn eine Funktion schon als Template existieren würde und ich sie dann überladen würde? Bsp.:

    //blah.h, unbekannte Funktionsdeklaration
    template<typename T> void Increase(T &in);
    
    //main.cpp
    //meine Alternativfunktion, falls es keine Funktion für meinen Aufruf gibt
    template<typename T> void Increase(T &in)    //ERROR: multiple declaration
    {
        cout << "SFINAE" << endl;
    }
    
    int main()
    {
        int xx = 0;
        Increase(xx);
    }
    

    3. Das mit der Bekämpfung von Symptomen verstehe ich nicht ganz:
    Wenn ich jetzt keine Tupels mit 15 Typen habe und kein typsicheres printf, ist das dann ein Symptom, wegen dem Variadic Templates eingeführt wurden? Oder ist das ein allgemeines Problem? Wenn ich aus Bequemlichkeit für den User keine verschieden benannten Mathematikfunktionen für verschiedene Zahlentypen einführen will, ist das dann ein Symptom, wegen dem man Funktionsüberladung einführt?

    4. Was meinst du mit Introspection/Reflexion? Soll ich wxWidgets umschreiben?



  • wxSkip schrieb:

    2. Okay, SFINAE kannte ich so noch nicht. Würde ich das für Konstruktoren verwenden, müsste ich doch alle wxWidgets-Klassen modifizieren, oder?

    SFINAE hilft dir nicht wirklich weiter, da es sich nur auf die Funktionssignatur bezieht. Aber du hast ja schon erkannt, dass die automatische Auswahl des richtigen Codes in deinem Beispiel nicht funktioniert, warum willst du also krampfhaft versuchen, irgendwelche anderen Sprachmittel dazu hinzubiegen? Alternative Ansätze wurden ja bereits genannt.

    wxSkip schrieb:

    Außerdem (aus Interesse), was würde passieren, wenn eine Funktion schon als Template existieren würde und ich sie dann überladen würde?

    SFINAE heisst nicht Templates überladen. Gerade solche Probleme sollte es unter anderem lösen. Schau dir das Konzept noch etwas genauer an, z.B. bei Wikibooks.

    wxSkip schrieb:

    Oder ist das ein allgemeines Problem?

    Ja, das ist ein allgemeines Problem. Im Gegensatz dazu sehe ich keinen Nutzen in deinem Feature.

    wxSkip schrieb:

    Wenn ich aus Bequemlichkeit für den User keine verschieden benannten Mathematikfunktionen für verschiedene Zahlentypen einführen will, ist das dann ein Symptom, wegen dem man Funktionsüberladung einführt?

    Der Gedanke hinter Funktionsüberladung ist Abstraktion (genau gesagt Polymorphie), nicht Bequemlichkeit.



  • Danke, ich hatte SFINAE wirklich falsch verstanden. Bei meinem Problem hilft mir das aber jetzt nicht weiter, denn 1. ist ein Konstruktor keine Memberfunktion und 2. kann ich alternativ nicht prüfen, ob die Klasse die Memberfunktion Create() hat, oder?
    Und was Shade Of Mine mit dem perfect forwarding jetzt gemeint hat, ist mir immer noch nicht klar.



  • Ich verstehe nicht das Problem. Normalerweise kompiliere ich Software und liefere sie aus. Ich uebersetze sie auf meinem System. Wenn ich std::unordered_set verwenden moechte, dann stelle ich sicher, dass sie vorhanden ist. Wenn ich eine bestimmte Bibliotheksfunktion benutzen moechte, dann stelle ich sicher, das die Bibliothek auch vorhanden ist. Ist sie es auf dem Zielsystem nicht, so liefere ich sie mit oder der Anwender muss sich selbst drum kuemmern. Das gleiche gilt, wenn der Anwender sie Software selbst uebersetzen moechte. Das gleiche auch fuer die verschiedenen Konstruktoren.

    Ich verstehe nicht, warum das ein Problem der Programmiersprache C++ ist. Warum ist das ueberhaupt ein Problem?



  • knivil schrieb:

    Ich verstehe nicht das Problem. Normalerweise kompiliere ich Software und liefere sie aus. Ich uebersetze sie auf meinem System. Wenn ich std::unordered_set verwenden moechte, dann stelle ich sicher, dass sie vorhanden ist. Wenn ich eine bestimmte Bibliotheksfunktion benutzen moechte, dann stelle ich sicher, das die Bibliothek auch vorhanden ist. Ist sie es auf dem Zielsystem nicht, so liefere ich sie mit oder der Anwender muss sich selbst drum kuemmern. Das gleiche gilt, wenn der Anwender sie Software selbst uebersetzen moechte. Das gleiche auch fuer die verschiedenen Konstruktoren.

    Ich verstehe nicht, warum das ein Problem der Programmiersprache C++ ist. Warum ist das ueberhaupt ein Problem?

    Ja, das ist wahrscheinlich meistens kein Problem. Benutze ich Code::Blocks und wechsle lediglich den Compiler auf eine ältere Version, könnte es sein, dass die Library nicht vorhanden ist und das kann ich nicht prüfen, da ich kein eigenes Build Script verwende. Könnte ich machen, weiß aber nicht wie's geht (und da bin ich halt faul ;)). Zum zweiten Problem: Das ist eine Frage der Kapselung - die sich meistens nicht stellt, in meinem Fall aber schon. Könnte man mit mehr Aufwand sicher auch anders lösen. Generell fällt mir im Moment grad kein weiterer Anwendungsfall ein, ich weiß nicht, ob und wie viele es da gäbe.



  • Hm... könnte man mit so etwas nicht auch Concepts ersetzen?

    template<typename T> class MyVector
    {
            static_assert(not_compile(T() == T()), "Vector type needs an equality operator");
    }
    


  • Schon wieder jemand, der die Macht von Template-Metaprogrammierung unterschätzt. 🙂

    C++ kann bereits jetzt überprüfen, ob ein Typ den operator== unterstützt (das könnte man allerdings noch sauberer lösen):

    template <typename T>
    T create();
    
    struct x47
    {
    	char x[47];
    };
    
    struct equality_comparator
    {
    	template <typename T>
    	equality_comparator(const T&);
    };
    
    x47 operator== (equality_comparator, equality_comparator);
    
    template <typename T>
    struct is_equality_comparable
    {
    	static const bool value = 
    		sizeof(x47) != sizeof(create<T>() == create<T>());
    };
    

    Anwendung:

    #include <iostream>
    
    struct not_comparable {};
    
    int main()
    {
    	std::cout << is_equality_comparable<int>::value << std::endl;
    	std::cout << is_equality_comparable<not_comparable>::value << std::endl;
    }
    

    😮 💡



  • Das ist ja mal eine Interessante Möglichkeit. Aber ehrlich gesagt versteh ich nicht ganz warum das eigentlich funktioniert.
    Könntest du den Code evtl. etwas erläutern Nexus?

    Gruß Gate



  • Man benutzt Überladungsauflösung, um herauszufinden, ob ein operator== existiert:

    • Falls T einen Gleichheitsoperator bereitstellt, werden die beiden Operanden bei
    create<T>() == create<T>()
    

    als T ausgewertet, das Ergebnis hat höchstwahrscheinlich den Typ bool . Man benutzt create<T>() statt T() , um keinen Defaultkonstruktor zu erzwingen.

    • Falls nicht, wird die implizite Konvertierung der beiden Operanden zu equality_comparator vorgenommen. Der entsprechende überladene operator== gibt ein x47 -Objekt zurück.

    Nun prüft der sizeof -Operator die Grösse des Ausdrucks create<T>() == create<T>() . Bei nicht vorhandenem operator== hat der Ausdruck die Grösse von x47 , da zwei equality_comparator -Objekte verglichen werden. Bei vorhandenem ist der resultierende Typ (meist bool ) mit grösster Wahrscheinlichkeit nicht 47 Bytes gross. Anzumerken ist hierbei, dass sizeof den Ausdruck nie wirklich auswertet – deshalb genügen auch die Funktionsdeklarationen.

    Diese kleine Unsicherheit ist ein Teil, den man sauberer lösen könnte. Zudem sollte die ganze Implementierung in einen Namensraum detail verfrachtet werden, um Konvertierungen im Benutzercode zu vermeiden. Im Weiteren hat der Ansatz das Problem, dass der Code mit private oder protected Member- operator== nicht kompiliert.



  • Hey, super Erklärung vielen Dank dafür.
    Zwei Kleinigkeiten noch:
    1. Warum hat x47 ein Array von 47 Bytes? Gibt es einen speziellen Grund dafür so einen hohen Wert zu nehmen? In meinem Test hat es auch mit char x[2] beispielsweise geklappt.
    2. Ist equality_comparator überhaupt notwending? In meinem Test funktionierte es auch mit

    template <typename T> x47 operator==(T,T)
    

    Dies noch in einen Detail-Namespace verfrachtet wegen versehentlicher Konvertierungen und man sollte auf der sicheren Seite sein.
    Oder übersehe ich hier etwas, weswegen equality_comparator notwendig wäre?

    Gruß Gate


Anmelden zum Antworten