(Templates) In erbender Klasse Schnittstelle ändern



  • Erstmal zu SFINAE:

    Das bedeutet doch, dass ein Template(sei es eine parametrisierte Klasse oder Funktion), wenn es nicht ganz vom Typ her passt so abgewandelt wird, dass keinen Fehler wirft, sondern einfahc nicht beachtet wird. Oder?!

    Was ich vorhabe muss ich mir im Prinzip selbst mal klar machen.

    Ich habe als Aufgabe, dass ich einen Graphen erzeugen muss und darauf eine Suche anwenden will.

    Ich habe als Grundlage 2 verschiedene Graphrepräsentationen (Boost Graph Library und was eigenes) sowie auch jeweils 2 Suchmethoden. (Suchmethode soll hier nicht Algorithmus bedeuten, dieser ist nämlich bei beiden derselbe. Aber Variante A ist eine Funktion aus der BGL und B ist meine eigene Implementierung.)

    Ich will nun durch einen Schalter einstellen, ob ich Boost oder mein eigenes benutze.

    Und ich glaube mir wird grad klar, warum ich mich so verrenne. Ich habe weder gut geplant noch die Regel Nummer 1 befolgt. Da der Graph sehr groß wird, dachte ich mir, Speicher sparen, Laufzeit sparen usw. Premature Optimization halt.

    Passt auf, ich zieh das ganze Ding nochmal halbwegs neu auf. Mal sehen was dann passiert.



  • Das bedeutet doch, dass ein Template(sei es eine parametrisierte Klasse oder Funktion), wenn es nicht ganz vom Typ her passt so abgewandelt wird, dass keinen Fehler wirft, sondern einfahc nicht beachtet wird. Oder?!

    Ja, aber dein Beispiel enthielt ja keine Templates.

    Ich will nun durch einen Schalter einstellen, ob ich Boost oder mein eigenes benutze.

    Sollte dieser Schalter nicht lieber ein Makro o.ä. sein?



  • Sone schrieb:

    enable_if müsste hier bspw. auf den Rückgabetyp angewandt werden.

    Was äquivalent zum Code von Skym0sh0 wäre und hier genau gleich falsch.

    Was übrigens auch noch ginge, wäre static_assert in der Methode, das wäre direkter da es sich hier gerade nicht um SFINAE handelt*.

    Geht hier trotzdem nicht.

    P.S.: Wenn du unsicher bist, dann probiere mal den specifier override (C++11).

    Hauptsache mit Features um sich werfen.

    Sone schrieb:

    Ich glaube der Fachausdruck hierfür heisst SFINAE?!

    Nein. 🙂

    Was genau ist an enable_if nicht SFINAE?

    Edit: Ahh, ich habe wieder zu schnell gelesen.

    Wie wärs, vor dem Postem mal zu überlegen?

    Aber das ist doch schwachsinnig! So wird das doch erst zur Compilezeit klar, ob die Deklaration passt oder nicht.

    Wäre es etwa besser, das erst zur Runtime zu erfahren!? 😮 Der Satz ist ziemlich schwachsinnig.

    Also ich bin ein wenig verwirrt

    Ich auch, nachdem ich deinen Post gelesen habe. Der Wunsch von Skym0sh0 ist zwar seltsam, aber genau definiert.

    Sone schrieb:

    Das bedeutet doch, dass ein Template(sei es eine parametrisierte Klasse oder Funktion), wenn es nicht ganz vom Typ her passt so abgewandelt wird, dass keinen Fehler wirft, sondern einfahc nicht beachtet wird. Oder?!

    Ja, aber dein Beispiel enthielt ja keine Templates.

    Offensichtlich ist das ihm nicht ganz bewusst. Vielleicht hätte man ihn darauf hinweisen können?

    Ich will nun durch einen Schalter einstellen, ob ich Boost oder mein eigenes benutze.

    Sollte dieser Schalter nicht lieber ein Makro o.ä. sein?

    constexpr ist mächtiger. Z.B. Simultane Nutzung.



  • Skym0sh0 schrieb:

    Bei Compiletime Inheritance sieht das aber schon anders aus:

    Naja, es geht nicht mehr zur Runtime, aber dann macht es keinen Sinn, virtual zu verwenden.

    Wie man dein Anliegen umsetzen könnte:

    template<bool Use>
    struct Enable : std::false_type
    {
        static const bool UseIt = false;
        typedef /* komplexe Deklaration */ Type;
    };
    
    template<>
    struct Enable<true> : std::true_type
    {
        static const bool UseIt = true;
        typedef /* andere komplexe Deklaration */ Type;
    };
    
    typedef Enable<true/* oder manchmal false*/> MyTypeEnabler;
    typedef MyTypeEnabler::Type MyType;
    
    // Derive1.h
    class Derive1 : public Base
    {
      int method_impl(MyType const& x, std::true_type)
      {
        x.specialMethodeDieAbhaengigVonEnableTrueFalseIst();
        return 0;
      }
      int method_impl(MyType const& x, std::false_type)
      {
        return Base::method(x);
      }
    
      int method(MyType const x)
      {
        return method_impl(x, MyTypeEnabler{});
      }
    };
    

    Kann man natürlich noch abstrahieren und die Nutzung vereinfachen, aber wenn das nicht oft vorkommt (ja, es ist ein Designfehler, aber in grösseren Designs dürfen kleinere Fehler vorkommen -- deshalb stört es aber auch nicht, wenn das eine kleine Syntaxverrenkung ist), wäre das so ok.



  • facepalm, dein erster Post ist so bescheuert, das ich gar nicht darauf antworten möchte. Ich mach es trotzdem.

    facepalm schrieb:

    Sone schrieb:

    enable_if müsste hier bspw. auf den Rückgabetyp angewandt werden.

    Was äquivalent zum Code von Skym0sh0 wäre und hier genau gleich falsch.

    Nein, denn dann ist die Signatur anders.

    facepalm schrieb:

    Was übrigens auch noch ginge, wäre static_assert in der Methode, das wäre direkter da es sich hier gerade nicht um SFINAE handelt*.

    Geht hier trotzdem nicht.

    Doch, geht wunderbar.

    facepalm schrieb:

    P.S.: Wenn du unsicher bist, dann probiere mal den specifier override (C++11).

    Hauptsache mit Features um sich werfen.

    Nein, Hauptsache er lernt was und kann den Code einfacher schreiben.
    (Hauptsache irgendwelche Kritikpunkte finden, was? Trottel)

    facepalm schrieb:

    Sone schrieb:

    Ich glaube der Fachausdruck hierfür heisst SFINAE?!

    Nein. 🙂

    Was genau ist an enable_if nicht SFINAE?

    Bist du irgendwie bescheuert?
    std::enable_if führt hier nur dazu, dass nichts kompiliert.

    facepalm schrieb:

    Edit: Ahh, ich habe wieder zu schnell gelesen.

    Wie wärs, vor dem Postem mal zu überlegen?

    Und mal wieder hat deine Antwort keinen Sinn. Ich habe überlegt. Aber bevor ich überlege, lese ich die Posts durch. Und da habe ich mich verlesen.

    facepalm schrieb:

    Aber das ist doch schwachsinnig! So wird das doch erst zur Compilezeit klar, ob die Deklaration passt oder nicht.

    Wäre es etwa besser, das erst zur Runtime zu erfahren!? 😮 Der Satz ist ziemlich schwachsinnig.

    Ja, das stimmt, der Satz ist bescheuert. Es sollte heißen, zur Laufzeit.
    (Ich dachte, er will bspw. den Parameter überprüfen o.ä.).

    facepalm schrieb:

    Also ich bin ein wenig verwirrt

    Ich auch, nachdem ich deinen Post gelesen habe. Der Wunsch von Skym0sh0 ist zwar seltsam, aber genau definiert.

    Nein, ich bin jetzt durch beide verwirrt. 😮

    facepalm schrieb:

    Sone schrieb:

    Das bedeutet doch, dass ein Template(sei es eine parametrisierte Klasse oder Funktion), wenn es nicht ganz vom Typ her passt so abgewandelt wird, dass keinen Fehler wirft, sondern einfahc nicht beachtet wird. Oder?!

    Ja, aber dein Beispiel enthielt ja keine Templates.

    Offensichtlich ist das ihm nicht ganz bewusst. Vielleicht hätte man ihn darauf hinweisen können?

    Hab ich das nicht genau durch meine Aussage gemacht?



  • Ok, wartet mal Jungs bevor ihr euch beide zerreisst. Ich glaube ich fange ganz vorne an:

    Anforderungen:
    - Ich brauche eine Graph Repräsentation (sprich eine Graph-Klasse sagen wir mal)
    - diese sollte logischerweise eine Reihe von Operationen bereitstellen (z.B. Kanten oder Knoten hinzufügen)
    - Ich brauche einen Shortest-Path Algorithmus (z.B. A*), der sich auch auf die Graph-Klasse anwenden lässt

    Ok, das ist nicht schwer. Da gibts ja sicherlich was, und siehe da Boost hält mit der BGL genau das richtige bereit. Hoch templatarisiert, aber damit auch extrem parametrisierbar. So weit so gut. Auch die Implementierung war nach einigen Anstrengungen kein Problem und es lieft alles

    Jetzt kommt allerdings das Problem, dass das ganze Projekt im Rahmen einer wissenschaftlichen Arbeit abläuft (Zeitdruck!) und ich eventuell den A* Algorithmus entweder verändern oder gegen ein Derivat austauschen muss (MA* oder SMA* z.B., also die speichersparenden Brüder).

    An dem Punkt dachte ich: "Okay, implementierst du selbst mal eine Graph-Klasse und dem A*". Das habe ich dann gemacht, getestet und es ging ganz gut. Die Kopplung zwischen Graph-Repräsentation und Pathfinding-Algorithmus ist jedoch sehr hoch.

    Ich wollte dann in meinem Projekt den Boost Graphen so abkapseln, dass ich beiden Graph-Implementierungen ein gemeinsames Interface geben kann und sie so einfach austauschen kann. Aber tausche ich die Graphen aus muss ich logischerweise die Suchalgortihmen auch ersetzen.

    Derzeitig bin ich so dran gegangen:
    - Ich habe ein Interface für beide Graphen und beide Algorithmen
    - Ich habe eine Abstrakte Fabrik, die durch je eine Klasse implementiert wird, welche entweder den Boost Graph & Boost A* erzeugt oder halt meine eigene Implementierung. (Normale Polymorphie zur Laufzeit mit Zeigern)

    Ein großes Problem ist dabei die Identifikation von Knoten. Je nach Wahl der Datenstrukturen für boost::adjacency_list werden die Knoten in einem Vector gespeichert und die Identifikation läuft über std::size_t. Es können aber auch z.B. Iteratoren einer Map sein. (Analog gilt das gleiche auch für Kantenidentifikation).
    D.h. dadurch, dass ich meine eigene Implementierung liefere bin ich quasi gebunden, die Datenstrukturen sowohl bei Boost als auch bei meiner Implementierung jetzt nicht mehr zu verändern.



  • Ok, das ist nicht schwer. Da gibts ja sicherlich was, und siehe da Boost hält mit der BGL genau das richtige bereit. Hoch templatarisiert, aber damit auch extrem parametrisierbar. So weit so gut. Auch die Implementierung war nach einigen Anstrengungen kein Problem und es lieft alles

    Respekt. Das ist auch von dir, bist nicht davon losgekommen... 🤡

    Gut, ich verstehe was du möchtest. Da ist tatsächlich facepalms Code nah dran, an dem was ich vorschlagen würde. Problem ist jedoch, dass du durch

    int method_impl(MyType const& x, std::true_type)
      {
        x.specialMethodeDieAbhaengigVonEnableTrueFalseIst();
        return 0;
      }
      int method_impl(MyType const& x, std::false_type)
      {
        return Base::method(x);
      }
    
      int method(MyType const x)
      {
        return method_impl(x, MyTypeEnabler{});
      }
    

    Gezeigt hast, facepalm, dass du das Idiom nicht verstehst. Es ist bei Templates anzuwenden, und so zu implementieren dass die Funktionen die nicht benutzt werden gar nicht erst kompiliert werden müssen. Da können wir ja gleich schreiben:

    int method(MyType const x)
      {
      	if( MyTypeEnabler::value )
    	{
    		x.specialMethodeDieAbhaengigVonEnableTrueFalseIst();
    		return 0;
    	}
    
    	return Base::method(x);
      }
    

    @Skym0sh0:
    Wenn es dir also auch egal ist, das beide Implementierungen mitkompiliert werden, egal welche benutzt wird*, dann schreibst du auch die Enabler-Klasse so um:

    template<bool Use>
    struct Enable : std::integral_constant<bool, Use>,
                    std::conditional<UseIt, /* komplexe Deklaration */, /* andere komplexe Deklaration */>
    {
        static constexpr bool UseIt = Use;
    };
    

    *: Daher schlug ich Makros vor, die das Problem gelöst hätten (du bindest nur einen speziellen Header ein, usw.)



  • Sone schrieb:

    Ok, das ist nicht schwer. Da gibts ja sicherlich was, und siehe da Boost hält mit der BGL genau das richtige bereit. Hoch templatarisiert, aber damit auch extrem parametrisierbar. So weit so gut. Auch die Implementierung war nach einigen Anstrengungen kein Problem und es lieft alles

    Respekt. Das ist auch von dir, bist nicht davon losgekommen... 🤡

    Was meinst du damit??
    War das falsch die zu benutzen?



  • Nein, nein. Wenn du damit arbeiten kannst, ist doch alles wunderbar.



  • Nein, nur die Library ist berüchtigt dafür, ...
    Naja, hat man ja bei dir gepostet:

    Ethon schrieb:

    Ich muss sagen dass ich mit Boost.Graph garnichts auf die Reihe gebracht habe - und dir geht's anscheinend genauso. Wird wohl nicht umsonst empfohlen sich das Buch dazu zu kaufen.

    Von daher finde ich die Library einfach nur schlecht - eine gute Library ist nie so schlecht dokumentiert bei gleichzeitig so hohem Scchwierigkeitsgrad.

    Nexus schrieb:

    Als ich mal etwas mit Graphen machen musste und Boost.Graph angeschaut habe, fand ich das API die Hölle. Der Generizitätswahnsinn und die schlecht dokumentierten und impliziten Konzepte (im Gegensatz zu Typen, die man im Code nachschauen könnte) machen es meiner Meinung nach sehr schwer, vernünftig mit der Bibliothek zu arbeiten.

    Michael E. schrieb:

    Bei Boost.Graph habe ich kein Land gesehen.

    Jester schrieb:

    Imo ist das das einzig brauchbare an der doku: auf jeder seite ganznrunter scrollen und dann den beispielcode anschauen...



  • Achso rum, das Respekt war dafür, dass ichs geschafft hab die Lib anzuwenden.

    Naja, im Nachhinein betrachtet ists schon recht einfach, wenn man einmal weiss wie das vorgesehen ist.


Anmelden zum Antworten