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



  • 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



  • Gate schrieb:

    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.

    Das ist ein willkürlicher Wert, der eine relativ kleine Wahrscheinlichkeit hat, dass irgendein Rückgabetyp eines benutzerdefinierten operator== s die gleiche Grösse besitzt. Man kann natürlich noch grössere Zahlen nehmen oder es gleich sauber lösen.

    Gate schrieb:

    2. Ist equality_comparator überhaupt notwending?

    Beim direkten Funktionstemplate können Probleme entstehen, falls der operator== für den Typ T nicht direkt existiert, sondern

    bool operator==(const U&, const U&);
    

    mit T implizit konvertierbar zu U . Denn sobald ein Funktionstemplate in den Überladungen vorkommt, wird dieses impliziten Konvertierungen vorgezogen. Also würde im Falle einer U -Überladung angegeben, der Typ T sei nicht equality comparable, obwohl der Ausdruck T == T Sinn macht.

    Andererseits ist es leider nicht so, dass equality_comparator dieses Problem löst. Da zwei Konvertierungen (zu U und zu equality_comparator ) existieren, liegt eine Mehrdeutigkeit vor. Ich habe versucht, der Klasse equality_comparator einen Konstruktor mit einer Ellipse zu geben, da diese bei der Überladungsauflösung zuletzt berücksichtigt wird und somit allfällige implizite Konvertierungen bevorzugt würden. Allerdings übersehe ich wohl etwas, die Mehrdeutigkeit existiert nach wie vor...



  • Danke für die Erläuterungen



  • Gate schrieb:

    Danke für die Erläuterungen

    Von mir auch 👍 Echt erstaunlich, was man da alles "rumwurschteln" kann.



  • Na endlich hab ich die Lösung gefunden, um herauszufinden, ob eine Funktion existiert (ich poste dann die vollständige Lösung für das wxWidgets-Problem, sobald ich sie gefunden habe).

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    struct False_help{char x[5];};
    
    template <typename T> auto SFINAE_has_size(T in) -> decltype(&T::size);
    False_help SFINAE_has_size(...);
    
    #define has_size_func(x) (sizeof(False_help) != sizeof(SFINAE_has_size(x)))
    
    int main()
    {
        cout << boolalpha << has_size_func(cout) << endl;
    }
    


  • Sodala, hier nach langem Herumprobieren hätten wir das Monsterbeispiel:

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    //test classes: 1 without Create-Function, 1 with Create(int)-Function and 1 with Create(int, string)-Function
    
    class Test_1
    {
        bool allright;
        public:
        Test_1() : allright(true) {}
    
        bool IsGood(){return allright;}
    };
    
    class Test_2
    {
        bool allright;
        public:
        Test_2() : allright(false) {}
        void Create(int p1, int p2 = 0){allright = true;}
    
        bool IsGood(){return allright;}
    };
    
    class Test_3
    {
        bool allright;
        public:
        Test_3() : allright(false) {}
        void Create(int p1, string p2, int p3 = 0){allright = true;}
    
        bool IsGood(){return allright;}
    };
    
    //SFINAE
    
    typedef struct {char x[2];} False_help;
    typedef char True_help;
    
    template<typename T> auto SFINAE_has_create(T *in) -> decltype(&T::Create);
    False_help SFINAE_has_create(...);
    
    template<typename T, typename... Args> True_help SFINAE_is_member_func_pointer(void (T::*)(Args...));
    template<typename T> True_help SFINAE_is_member_func_pointer(void (T::*)());
    False_help SFINAE_is_member_func_pointer(...);
    #define has_create_func(x) (sizeof(True_help) == sizeof(SFINAE_is_member_func_pointer(SFINAE_has_create(x))))
    
    template<typename T, typename... Args> True_help SFINAE_create_func_needs_string(void (T::*)(int, string, Args...));
    template<typename T, typename... Args> False_help SFINAE_create_func_needs_string(void (T::*)(int, Args...));
    
    #define create_func_needs_string(x) (sizeof(True_help) == sizeof(SFINAE_create_func_needs_string<x>(&x::Create)))
    
    template<typename T, bool create_needs_string> struct Call_right_create_func //will be used if create_needs_string == true
    {
        static void exec(T *in){in->Create(0, string());}
    };
    
    template<typename T> struct Call_right_create_func<T, false>
    {
        static void exec(T *in){in->Create(0);}
    };
    
    template<typename T, bool has_create> struct Create_if_true //will be used if has_create == true
    {
        static void exec(T *in){Call_right_create_func<T, create_func_needs_string(T)>::exec(in);}
    };
    
    template<typename T> struct Create_if_true<T, false>
    {
        static void exec(T *in){}
    };
    
    template<typename T> T *create_class()
    {
        T *tmp = new T();
        Create_if_true<T, has_create_func(tmp)>::exec(tmp); //if the type has a Create()-function, it will be called
        return tmp;
    }
    
    int main()
    {
        Test_1 *x1 = create_class<Test_1>();
        Test_2 *x2 = create_class<Test_2>();
        Test_3 *x3 = create_class<Test_3>();
    
        cout << boolalpha << x1->IsGood() << endl << x2->IsGood() << endl << x3->IsGood() << endl;
    }
    

    Und hier die Library-Version, wie sie mit wxWidgets funktionieren sollte (ungetestet, aber fast das gleiche wie oben, und das ist getestet):

    //SFINAE
    
    typedef struct {char x[2];} False_help;
    typedef char True_help;
    
    template<typename T> auto SFINAE_has_create(T *in) -> decltype(&T::Create);
    False_help SFINAE_has_create(...);
    
    template<typename T, typename... Args> True_help SFINAE_is_member_func_pointer(void (T::*)(Args...));
    template<typename T> True_help SFINAE_is_member_func_pointer(void (T::*)());
    False_help SFINAE_is_member_func_pointer(...);
    #define has_create_func(x) (sizeof(True_help) == sizeof(SFINAE_is_member_func_pointer(SFINAE_has_create(x))))
    
    template<typename T, typename... Args> True_help SFINAE_create_func_needs_string(void (T::*)(wxWindow *, int, wxString, Args...));
    template<typename T, typename... Args> False_help SFINAE_create_func_needs_string(void (T::*)(wxWindow *, int, Args...));
    
    #define create_func_needs_string(x) (sizeof(True_help) == sizeof(SFINAE_create_func_needs_string<x>(&x::Create)))
    
    template<typename T, bool create_needs_string> struct Call_right_create_func //will be used if create_needs_string == true
    {
        static void exec(T *in){in->Create(NULL, wxID_ANY, wxEmptyString);}
    };
    
    template<typename T> struct Call_right_create_func<T, false>
    {
        static void exec(T *in){in->Create(NULL, wxID_ANY);}
    };
    
    template<typename T, bool has_create> struct Create_if_true //will be used if has_create == true
    {
        static void exec(T *in){Call_right_create_func<T, create_func_needs_string(T)>::exec(in);}
    };
    
    template<typename T> struct Create_if_true<T, false>
    {
        static void exec(T *in){}
    };
    
    template<typename T> T *create_class()
    {
        T *tmp = new T();
        Create_if_true<T, has_create_func(tmp)>::exec(tmp); //if the type has a Create()-function, it will be called
        return tmp;
    }
    


  • Nächstes Mal bin ich vorsichtiger, wenn ich die Möglichkeiten von C++ aufzeigen will. Ob dieses Template-Gefrickel wirklich der richtige Weg ist, bleibt nämlich nach wie vor fragwürdig. Oder anders gesagt: Nur weil etwas möglich ist, ist es nicht sinnvoll.

    Im Normalfall ist es ein Anzeichen schlechten Designs, zuerst das Vorhandensein einer Funktionalität abzufragen und dann abhängig von ihr darauf zu reagieren. Zumindest im grösseren Rahmen sollten solche expliziten Fallunterscheidungen vermieden werden. Und zwar aus ähnlichen Gründen wie virtuelle Funktionen einer if-else -Kaskade vorzuziehen sind.

    Im konkreten Fall hier versuchst du etwas nachzubauen, das wxWidgets bewusst nicht anbietet. Es ist nämlich eine wesentliche Designentscheidung, wenn Klassen keine Defaultkonstruktoren besitzen. Diese deutet darauf hin, dass bei der Konstruktion von Objekten bereits genügend Informationen vorhanden sein müssen, um eine sinnvolle Initialisierung vorzunehmen. Mit deinem Ansatz arbeitest du an diesem Design vorbei und erzeugst Dummy-Objekte, die du nachträglich füllen musst. Warum? Sowas ist nie gut.



  • Nexus schrieb:

    Nächstes Mal bin ich vorsichtiger, wenn ich die Möglichkeiten von C++ aufzeigen will. Ob dieses Template-Gefrickel wirklich der richtige Weg ist, bleibt nämlich nach wie vor fragwürdig. Oder anders gesagt: Nur weil etwas möglich ist, ist es nicht sinnvoll.

    Im Normalfall ist es ein Anzeichen schlechten Designs, zuerst das Vorhandensein einer Funktionalität abzufragen und dann abhängig von ihr darauf zu reagieren. Zumindest im grösseren Rahmen sollten solche expliziten Fallunterscheidungen vermieden werden. Und zwar aus ähnlichen Gründen wie virtuelle Funktionen einer if-else -Kaskade vorzuziehen sind.

    Im konkreten Fall hier versuchst du etwas nachzubauen, das wxWidgets bewusst nicht anbietet. Es ist nämlich eine wesentliche Designentscheidung, wenn Klassen keine Defaultkonstruktoren besitzen. Diese deutet darauf hin, dass bei der Konstruktion von Objekten bereits genügend Informationen vorhanden sein müssen, um eine sinnvolle Initialisierung vorzunehmen. Mit deinem Ansatz arbeitest du an diesem Design vorbei und erzeugst Dummy-Objekte, die du nachträglich füllen musst. Warum? Sowas ist nie gut.

    😃
    Ich wollte eigentlich
    1.So eine Art Feeling wie beispielsweise bei Java (da hab ich zb SWT verwendet) zu ermöglichen. Mir ist schon klar, dass die so erstellten Objekte gar nicht angezeigt werden. Bei SWT kann man beispielsweise einfach das Objekt erstellen und danach die Parameter setzen, die man möchte. (ist das bei Qt nicht auch so?)
    2. Vektoren für Pointer erstellen, die ihre Member von selbst Initialisieren (und zwar gescheit, dass man damit arbeiten kann, und eben nicht nur Default-Initialisiert).



  • wxSkip schrieb:

    1.So eine Art Feeling wie beispielsweise bei Java (da hab ich zb SWT verwendet) zu ermöglichen. Mir ist schon klar, dass die so erstellten Objekte gar nicht angezeigt werden. Bei SWT kann man beispielsweise einfach das Objekt erstellen und danach die Parameter setzen, die man möchte. (ist das bei Qt nicht auch so?)

    Warum Objekte nicht sinnvoll initialisieren, sondern Parameter erst im Nachhinein setzen? Das öffnet lediglich Fehlerquellen und umgeht das Konzept der Konstruktoren. Dass andere Bibliotheken oder Sprachen sich diesbezüglich anders verhalten, ist doch kein Argument. Java vererbt auch an Orten, wo es keinen Sinn macht.

    wxSkip schrieb:

    2. Vektoren für Pointer erstellen, die ihre Member von selbst Initialisieren (und zwar gescheit, dass man damit arbeiten kann, und eben nicht nur Default-Initialisiert).

    myVector.push_back(/* (Zeiger auf) vollständig initialisiertes Objekt */)
    

    Ich seh das Problem nicht... Die gewünschte Selbst-Initialisierung liefert dir auch nur wieder ein Dummy-Objekt, das du noch bearbeiten musst.



  • Nexus schrieb:

    wxSkip schrieb:

    2. Vektoren für Pointer erstellen, die ihre Member von selbst Initialisieren (und zwar gescheit, dass man damit arbeiten kann, und eben nicht nur Default-Initialisiert).

    myVector.push_back(/* (Zeiger auf) vollständig initialisiertes Objekt */)
    

    Ich seh das Problem nicht... Die gewünschte Selbst-Initialisierung liefert dir auch nur wieder ein Dummy-Objekt, das du noch bearbeiten musst.

    myVector.resize(15);
    

    Ich versuche gerade, eine Art Compiler zu schreiben für eine Sprache, in der das auch ohne Konstruktoren gehen soll (nicht nach dem Motto "was musste ich da jetzt schon wieder und in welcher Reihenfolge angeben und warum?"). Da wäre es für mich eine Erleichterung, das direkt so nach C++ übertragen zu können.



  • wxSkip schrieb:

    myVector.resize(15);
    

    Ja, das ist mir schon klar. Ich kann auch nicht std::sort() auf Listen anwenden. Weil beide Funktionen eben Voraussetzungen haben. resize() erfordert einen Standardkonstruktor, sort() Random-Access-Iteratoren.

    Aber darum geht es sowieso nicht. Ich habe ja erklärt, warum dein Vorgehen Nachteile mit sich bringt und wieso es im Prinzip ein Rückschritt in C++ ist, wo Kapselung und damit auch Klasseninvarianten einen hohen Stellenwert haben.

    wxSkip schrieb:

    nicht nach dem Motto "was musste ich da jetzt schon wieder und in welcher Reihenfolge angeben und warum?

    Das ist natürlich ein guter Grund. Nur ist die Frage, ob er die Nachteile wert ist? Du kannst zum Beispiel auch keine temporären Objekte mehr an Funktionen übergeben.

    Bei einem eigenen Projekt ist es auch wieder was Anderes, dann könntest du so ein Design wenigstens konsistent durchziehen. Aber bei einer bestehenden Bibliothek würde ich mir solche Eingriffe gut überlegen, da sie wie schon erwähnt Designüberlegungen des Entwicklers ausser Kraft setzen. Es gibt ja auch andere Möglichkeiten wie das Named Constructor Idiom, das zumindest in die Richtung geht.

    Aber ich denke, generell bist du in C++ schlecht aufgehoben, wenn du Funktionsargumente beim Aufruf unbedingt benennen willst. Auch wenn es Möglichkeiten wie Boost.Parameter gibt, sind diese etwas zwiespältig.



  • Nexus schrieb:

    resize() erfordert einen Standardkonstruktor

    Ja, und jetzt eben nicht mehr.

    Nexus schrieb:

    Du kannst zum Beispiel auch keine temporären Objekte mehr an Funktionen übergeben.

    Sorry, das musst du mir nochmal erklären.

    Nexus schrieb:

    Aber ich denke, generell bist du in C++ schlecht aufgehoben, wenn du Funktionsargumente beim Aufruf unbedingt benennen willst.

    Will ich vielleicht in meiner eigenen Programmiersprache, aber in C++ nicht. Ich will nur Zeiger automatisch auf ein gültiges Objekt zeigen lassen.



  • wxSkip schrieb:

    Ja, und jetzt eben nicht mehr.

    Ja, aber auch nur, weil du am Design herumbiegst. Du baust Funktionalität nach, die bewusst nicht angeboten wurde. Verstehst du nicht, was ich dir schon die ganze Zeit sagen will?

    wxSkip schrieb:

    Nexus schrieb:

    Du kannst zum Beispiel auch keine temporären Objekte mehr an Funktionen übergeben.

    Sorry, das musst du mir nochmal erklären.

    Ich kenne wxWidgets zu wenig. Aber ein mögliches Einsatzszenario wäre z.B. irgendeine Methode

    AddComponent(Component* newComponent);
    

    die so aufgerufen werden könnte:

    myButtons.AddComponent(new Button("Text", 60, 20));
    

    Dabei wird newButton gleich im Ausdruck erzeugt. Wenn du nun auf die Konstruktoren verzichtest, ist jeweils sowas nötig:

    Button* button = CreatePseudoEmptyButton();
    button->SetText("Text");
    button->SetSize(60, 20);
    myButtons.AddComponent(button);
    

    was neben der Umständlichkeit auch button in den umliegenden Scope einführt. Ist zwar kein grosses Problem, für mich wäre es allerdings unnötige eine Einschränkung.

    wxSkip schrieb:

    Ich will nur Zeiger automatisch auf ein gültiges Objekt zeigen lassen.

    Mit deinem Vorgehen erreichst du das genaue Gegenteil! Das Objekt ist zwar insofern "gültig", als der Zugriff kein undefiniertes Verhalten auslöst, aber logisch gesehen ist es in einem nicht sinnvollen Zustand. Eine Schaltfläche ohne Text und Grösse kannst du nicht brauchen. Es ist immer nachträglich eine "Initialisierung" nötig.

    Du hast mir immer noch nicht erklärt, warum du Buttons nicht erst dann einfügen kannst, wenn sie vollständig initialisiert sind. Was bringt dir ein resize() , das dir etliche Dummy-Objekte erzeugt, die du vor der Benutzung ohnehin nochmals bearbeiten musst?


  • Administrator

    Kleine Bemerkung am Rande; Die Signatur von resize lautet:
    void resize(size_type sz, T c = T());

    @Nexus,
    Ich kann dir sagen, wieso wxSkip das so will, weil er von wxWidgets schlechtes C++ Design gelernt hat.
    1. Müssen alle Widgets in wxWidgets per new konstruiert werden.
    2. Alle Widgets in wxWidgets haben einen Default-Konstruktor und eine Create-Funktion. Das Widget ist mit dem Default-Konstruktor in einem ungültigen Zustand, bis man die Create-Funktion aufgerufen hat. Hatten wir nicht einmal eine Diskussion über solche Init-Funktionen? Kann sie gerade nicht mehr finden.

    Grüssli



  • Nexus schrieb:

    wxSkip schrieb:

    Ja, und jetzt eben nicht mehr.

    Ja, aber auch nur, weil du am Design herumbiegst. Du baust Funktionalität nach, die bewusst nicht angeboten wurde. Verstehst du nicht, was ich dir schon die ganze Zeit sagen will?

    Ja, ich verstehe, dass du sagen willst, dass diese Funktionalität bewusst nicht angeboten wurde. Ich will sie in meiner Programmiersprache aber anbieten. Was soll ich sonst machen, außer das gleiche in C++ umzusetzen? Mir kommt es ja nicht auf jedes Geschwindigkeitsquäntchen an.

    Nexus schrieb:

    wxSkip schrieb:

    Nexus schrieb:

    Du kannst zum Beispiel auch keine temporären Objekte mehr an Funktionen übergeben.

    Sorry, das musst du mir nochmal erklären.

    Ich kenne wxWidgets zu wenig. Aber ein mögliches Einsatzszenario wäre z.B. irgendeine Methode

    AddComponent(Component* newComponent);
    

    die so aufgerufen werden könnte:

    myButtons.AddComponent(new Button("Text", 60, 20));
    

    Dabei wird newButton gleich im Ausdruck erzeugt. Wenn du nun auf die Konstruktoren verzichtest, ist jeweils sowas nötig:

    Button* button = CreatePseudoEmptyButton();
    button->SetText("Text");
    button->SetSize(60, 20);
    myButtons.AddComponent(button);
    

    was neben der Umständlichkeit auch button in den umliegenden Scope einführt. Ist zwar kein grosses Problem, für mich wäre es allerdings unnötige eine Einschränkung.

    wxSkip schrieb:

    Ich will nur Zeiger automatisch auf ein gültiges Objekt zeigen lassen.

    Mit deinem Vorgehen erreichst du das genaue Gegenteil! Das Objekt ist zwar insofern "gültig", als der Zugriff kein undefiniertes Verhalten auslöst, aber logisch gesehen ist es in einem nicht sinnvollen Zustand. Eine Schaltfläche ohne Text und Grösse kannst du nicht brauchen. Es ist immer nachträglich eine "Initialisierung" nötig.

    Du hast mir immer noch nicht erklärt, warum du Buttons nicht erst dann einfügen kannst, wenn sie vollständig initialisiert sind. Was bringt dir ein resize() , das dir etliche Dummy-Objekte erzeugt, die du vor der Benutzung ohnehin nochmals bearbeiten musst?

    Das resize() mit Dummy-Objekten verhindert mir eine Access violation beim Zugriff auf einen durch resize() erzeugten Pointer, genauer gesagt, das Objekt, auf den er zeigt.
    Ich glaube, ich sollte einmal ein Beispiel machen.
    Habe ich in meiner Programmiersprache einen Code:

    Fenster: fenster  //Default-Fenster, wird auf dem Desktop mit einer Default-Größe erstellt.
    Button[Zahl]: myvar  //std::vector<wxButton *> myvar, leer
    Button: knopf(Größe =  _Größe(100, 50); Ziel = fenster; BeiKlick = OnClick)  //Button-Variable "knopf", im Fenster "fenster". ID und Label werden per Default-Construction auf eine neue Zahl und "" gesetzt
    myvar[5] = knopf  //neinnein, kein Fehler, hier wird resize() benutzt
    myvar[4].Ziel = fenster  //so, und jetzt wird der andere Button angezeigt und keine Access violation (OK, er hat noch keine Größe)
    //außerden hat der andere Button keinen ungültigen Zustand, weil seine Create()-Funktion aufgerufen wurde!
    
    Ablauf: OnClick(ButtonKlickEreignis: erg)
    {
        //blah...
    }
    

    So, wie das mit den Arrays läuft, sieht man ja schon.
    Für mich ist es außerdem einfacher, für die Buttonerstellung diesen Code zu generieren:

    wxButton *knopf = create_class<wxButton>();
    knopf->SetParent(fenster);
    knopf->SetSize(wxSize(100, 50));
    knopf->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(App::OnClick));
    

    Wobei ich noch gar nicht gesagt habe, dass ich diesen Code generieren werde, ich wollte das SFINAE-Beispiel ursprünglich eigentlich nur für die Vektoren verwenden.



  • Dravere schrieb:

    weil er von wxWidgets schlechtes C++ Design gelernt hat.

    😞
    Und weil ich es für wxWidgets so benutzen muss?


  • Administrator

    wxSkip schrieb:

    Dravere schrieb:

    weil er von wxWidgets schlechtes C++ Design gelernt hat.

    😞
    Und weil ich es für wxWidgets so benutzen muss?

    Nö, eigentlich nicht. wxWidgets bietet schliesslich inzwischen auch direkt den Konstruktor an, welcher dann intern Create aufruft. Auch aus deinem gezeigten Code werde ich nicht schlau, wieso du diese Verzögerung einbauen müsstest.

    Grüssli


Anmelden zum Antworten