Template nur mit Kindklassen zulassen



  • Hallo zusammen

    hab jetzt ne Weile gesucht aber nichts gefunden.
    Weis jemand ob es irgendwie möglich ist ein Template nur mit einer vererbten Klasse zuzulassen?

    [cpp]class A            { virtual void Foo()=0; };
    class B : public A { void Foo(){}; };
    
    template <class [b]ADerived : public A[/b]>
    class CONT
    {
      [b]ADerived [/b]element;
    };[/cpp]
    

    Has3


  • Administrator

    Ist nicht möglich und macht auch keinen Sinn. Wieso sollte man eine Klasse nicht zulassen, weil sie nicht von einer Basisklasse erbt, aber das gleiche Konzept bereitstellt? Gibt keinen Grund, solch eine Einschränkung vorzunehmen.

    Grüssli



  • schade 😕
    aber "macht keinen Sinn" seh ich anders.
    Es wäre darum gegangen, dass die Basisklasse ein Interface zu den erbenden Klassen ist.
    Der Container greift über die Funktionen im Interface auf statische Daten in diesen Klassen zu und da wär es schlecht wenn jemand als typ ein int übergibt.

    trotzdem danke
    Gruß


  • Administrator

    Has3 schrieb:

    aber "macht keinen Sinn" seh ich anders.
    Es wäre darum gegangen, dass die Basisklasse ein Interface zu den erbenden Klassen ist.

    Du solltest nochmals die Templateprogrammierung anschauen, dann wirst du entdecken, dass dein Vorgehen absolut verkehrt ist. Man will eben genau solchen Basisklassen Interfaces entkommen mit den Templates und dadurch optimaleren Code generieren.

    Has3 schrieb:

    Der Container greift über die Funktionen im Interface auf statische Daten in diesen Klassen zu und da wär es schlecht wenn jemand als typ ein int übergibt.

    Wenn jemand einen int übergibt, gibt der Compiler einen Fehler aus, so oder so, da das Interface nicht stimmt. Diese Überprüfung findet automatisch statt, da braucht es keine Basisklasse.

    Grüssli



  • solange concepts noch nicht unterstützt werden, kannst du dein problem mit boost und type traits lösen -

    template <class T>
    class X 
    {
       BOOST_STATIC_ASSERT(boost::is_base_of<A, T>::value);
    };
    

    allerdings kannst du damit NUR von A abgeleitete klassen verwenden, auch wenn es möglicherweise sinnvoll wäre, andere einzusetzen (à la policies) - das wäre auch nicht weiter schlimm, denn bei der instanziierung des templates würde sich dein compiler dann auch mit einer fehlermeldung beklagen.

    template <class APolicy>
    class X
    {
       APolicy a;
       void foo ()
       {
          a.do_sth();
       }
    }; //kein fehler
    
    X<int> x;
    
    x.foo(); //fehler!
    

    außerdem ist es hier sinnlos, die methoden in der basisklasse virtuell zu machen, da das instanziierte template sowieso das nötige wissen über die policy haben muss.

    Dravere schrieb:

    Has3 schrieb:

    Der Container greift über die Funktionen im Interface auf statische Daten in diesen Klassen zu und da wär es schlecht wenn jemand als typ ein int übergibt.

    Wenn jemand einen int übergibt, gibt der Compiler einen Fehler aus, so oder so, da das Interface nicht stimmt. Diese Überprüfung findet automatisch statt, da braucht es keine Basisklasse.

    Grüssli

    die überprüfung findet nicht sofort automatisch statt.

    template <class T>
    class Foo
    {
       void bar ()
       {
          T t;
          t.foo_bar();
       }
    
       void foo ()
       {
         cout << "yay!\n"; 
       }
    };
    
    int main ()
    {
       Foo<int> f; //!
       f.foo(); 
    }
    

    kompiliert ohne probleme.

    das problem mit dem typen-check löst sich erst in C++0x mit concepts. das einzige, was wir jetzt zur verfügung haben, wäre die explizite instanziierung - und das lässt sich nicht automatisieren.


  • Administrator

    queer_boy schrieb:

    die überprüfung findet nicht sofort automatisch statt.

    template <class T>
    class Foo
    {
       void bar ()
       {
          T t;
          t.foo_bar();
       }
    
       void foo ()
       {
         cout << "yay!\n"; 
       }
    };
    
    int main ()
    {
       Foo<int> f; //!
       f.foo(); 
    }
    

    kompiliert ohne probleme.

    Und wenn man nun f.bar() aufrufen würde? :p
    Das ist eigentlich ein super Beispiel dafür, dass es eben kein Problem darstellt, wenn man einen "falschen" Typ einsetzt. So kann man sogar eine Klasse missbrauchen und es funktioniert trotzdem einwandfrei. Wenn man halt nur Teile vom Klassentemplate verwendet und nicht alles, dann muss der Templateparameter auch nur Teile des Konzeptes unterstützen.

    Grüssli



  • Dravere schrieb:

    ...
    Und wenn man nun f.bar() aufrufen würde? :p
    Das ist eigentlich ein super Beispiel dafür, dass es eben kein Problem darstellt, wenn man einen "falschen" Typ einsetzt....

    Ich bin da zwiegespalten ... einerseits finde ich das ganz gut, dass C++ da nur das Notwendige instantiiert. Andererseits wird's auch schnell unangenehm.

    Beispiel:

    // mein Code (V1.0):
    template <typename T>
    struct Simon2Client {
       void bar ()   {      T t;      t.foo_bar();   }
       void foo ()   {     cout << "yay!\n";   }
    };
    
    template <typename T>
    void useSimon2Client(T t) { t.foo(); }
    
    // Dein Code:
    int main ()
    {
       Simon2Client<int> clt;
       useSimon2Client(clt);
    }
    

    Nachdem der Code 10 Jahre lang prima läuft, Du schon lange nicht mehr in der Firma arbeitest, passe ich meinen ein wenig an:

    // mein Code (V1.3):
    ...
    template <typename T>
    void useSimon2Client(T t) { t.foo(); t.bar(); }
    

    -> Hoppla! Dein Code ist "gebrochen", weil Du meine implizite Schnittstelle mißachtet hast ...
    (bestimmt nicht das beste Beispiel, aber die Idee sollte rüberkommen)

    Gruß,

    Simon2.



  • Simon2 schrieb:

    -> Hoppla! Dein Code ist "gebrochen", weil Du meine implizite Schnittstelle mißachtet hast ...
    (bestimmt nicht das beste Beispiel, aber die Idee sollte rüberkommen)

    selbes kann ich auch ohne templates machen:

    std::string s;
    s.size(); //ok
    s.foo(); //kaputt 😞

    Und selbst wenn - wo ist das Problem? Der Code war korrekt und die Aenderung hat ihn kaputt gemacht - aber er bricht mit kompilieren schon ab.

    Absolut kein Problem und ein schoenes Beispiel warum templates toll sind.

    Wenn du naemlich das ganze bei laufzeit polymorphie machst und dort eine neue methode hinzufuegst oder eine entfernst, kann es sein dass du den code auch brichst, aber leise. dh es gibt keine statischen typchecks die dir helfen koennen und das programm macht einfach nur nicht dass was man erwartet.

    deshalb: dein beispiel zeigt schoen warum templates gut so sind wie sie sind.



  • Hi,

    bevor wir hier weiterdiskutieren, bitte ich Dich, Dir nochmal vor Augen zu halten:

    Simon2 schrieb:

    Ich bin da zwiegespalten ... einerseits finde ich das ganz gut, dass C++ da nur das Notwendige instantiiert. Andererseits wird's auch schnell unangenehm.

    Ergo: Ich bin keineswegs der Meinung, dass templates vom Teufel/unbrauchbar/prinzipiell abzulehnen/... seien, sondern sehe halt nicht nur Vorteile/Stärken in ihnen -> und bin ob dieses speziellen Features zwiegespalten.

    Nun aber zu Deiner Antwort:

    Shade Of Mine schrieb:

    Simon2 schrieb:

    -> Hoppla! Dein Code ist "gebrochen", weil Du meine implizite Schnittstelle mißachtet hast ...
    (bestimmt nicht das beste Beispiel, aber die Idee sollte rüberkommen)

    selbes kann ich auch ohne templates machen:

    std::string s;
    s.size(); //ok
    s.foo(); //kaputt 😞

    Naaaa, das ist schon ein wenig anders, weil std::string nie zugesagt hat, foo() zu bieten.
    Ich habe bei meinem obigen Beispiel eher eine Bibliothek o.ä. vor Augen.

    Shade Of Mine schrieb:

    Und selbst wenn - wo ist das Problem? Der Code war korrekt und die Aenderung hat ihn kaputt gemacht - aber er bricht mit kompilieren schon ab....

    Aber DU (also ich meine: Der Lib-Nutzer) hast den Fehler gemacht (nämlich die von Simon2Client geforderte Schnittstelle nicht erfüllt), was aber nicht auffiel (weil in der von Dir genutzten Funktion zufällig dieser Mangel nicht zum Tragen kam).
    Und ICH (also der Lib-Entwickler) habe keinen Fehler gemacht, weil ich nur die vorher von Simon2Client zugesagte Schnittstelle genutzt habe.

    Und bei aller Liebe zu Templates, sehe ich da schon ein Problem damit, dass ein Anwender einer Templatebibliothek "still" die Schnittstelle brechen kann.
    Ein hier im Forum bekanntes Praxisbeispiel ist ja der Einsatz von STL-Containern mit "nicht kopierbaren" Klassen, was laaaange gut gehen kann ... bis man sich plötzlich in einer totalen Sackgasse wiederfindet und seinen Code komplett umstricken muss.

    Aber (wie auch hier im Forum schon oft betont): Mit Concepts kann man das besser steuern.

    Gruß,

    Simon2.



  • Dafür gibts doch Dokumentationen... Der Standard dokumentiert, dass STL-Container nur mit kopierbaren Klassen problemlos funktionieren.
    Simon2Client sollte dokumentieren, dass bar() nur funktioniert wenn der Templateparameter eine Methode foo_bar() hat.
    Genauso sollte useSimon2Client dokumentieren, dass der Templateparameter eine Methode foo() hat - und dass die Fuktion eigentlich nur für Simon2Client-kompatible Templateparameter gedacht ist.
    Wie diese Dokumentation auszufallen hat ist Auslegungssache, Simon2 könnte bei seinem Beispiel durchaus zurecht sagen dass der Code übersichtlich genug ist um sich selbst zu dokumentieren - die STL-Container schreibens lieber explizit hin, weil die Benutzung des copy-Ctors irgendwo tief im Code vertseckt ist.

    Ich wäre deswegen nicht zwiegespalten @Simon - das Feature bietet dir die Freiheit, das geforderte Interface zu missachten, wenn du mit den Konsequenzen zu leben bereit bist (dass eben die Funktionalität nur eingeschränkt zur Verfügung steht). Insgesamt ists also keine Einschränkung. Wenn man auf der anderen Seite erzwingen würde dass die Klassen mit denen das Template instantiiert wird das volle Interface zur Verfügung stellen, dann schränkt man den Benutzer ein.

    OT:

    Shade Of Mine schrieb:

    [...] und dort eine neue methode hinzufuegst oder eine entfernst, kann es sein dass du den code auch brichst, aber leise. dh es gibt keine statischen typchecks die dir helfen koennen und das programm macht einfach nur nicht dass was man erwartet.

    Das erinnert mich an eine Diskussion über dynamische Typisierung *hust* 😉



  • pumuckl schrieb:

    ...Ich wäre deswegen nicht zwiegespalten @Simon...

    Danke für den Trost.
    👍 😋 😃

    pumuckl schrieb:

    ...
    Wie diese Dokumentation auszufallen hat ist Auslegungssache,...

    Das finde ich halt ein wenig betrüblich. Bei "normalen" Klassen und Funktionen ist die Schnittstelle im Prinzip sprachimmanent eindeutig (Parameter, Member, public/protected/private, "virtual-Polymorphie", ...) in der Deklaration vorgegeben.
    Da braucht's keine weitere (proprietäre, nicht-compilerprüfbare und üblicherweise komplett vernachlässigte 😉 ) Dokumentation oder gar Einsicht in die Implementation.
    Das finde ich einfach "schön" (elegant aber auch einfacher und sicherer), finde es schade, dass das bei templates nicht geht und hoffe, dass Concepts da zukünftig mehr ermöglichen.

    @"Einschränkungen für den Benutzer": Es ist in meinem Beispiel ist halt die Schnittstelle der templates nicht klar definiert:

    • MUSS der parametrisierende Typ alle Memberfunktionen unterstützen ? (=> stärkere Einschränkung für den Nutzer; Nutzer hat hier Fehler gemacht) oder
    • nicht. (=> weniger Einschränkung vür den Nutzer; Simon2 hat in V1.5 einen Fehler gemacht).

    (mal ganz davon abgesehen, dass die Verknüpfung von useSimon2Client() mit Simon2Client rein über den Namen auch nicht das Gelbe vom Ei ist)

    Diese Entscheidung stellt mir auch (IIRC) der Einsatz von Concepts frei: Ich (als Libschreiber) kann immer noch frei entscheiden, ob ich

    • mir mehr Einschränkungen auferlegen will (hier z.B., dass auch zukünftig use..() nur foo() nutzen darf)
    • oder dem Nutzer (dass sein Typ ebenfalls bar() unterstützen muss).

    Da wird das Bemühen des Libschreibers dann darin bestehen, möglichst kurze Concept-Listen zu benötigen...
    ... aber es wird eben standardisiert und automatisch überprüfbar dokumentiert.
    (Wie sagte mal ein Seminarleiter so schön zur Dokumentation: "Die einzige Wahrheit ist der Quellcode !")

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Aber DU (also ich meine: Der Lib-Nutzer) hast den Fehler gemacht (nämlich die von Simon2Client geforderte Schnittstelle nicht erfüllt), was aber nicht auffiel (weil in der von Dir genutzten Funktion zufällig dieser Mangel nicht zum Tragen kam).

    Es ist eben keine Schnittstellen Definition. Das ist der Sinn der Sache. Und das macht Templates richtig stark.

    Ich muss nicht alles unterstützen was ich unterstützen könnte. Deshalb ist es keine Schnittstellen Definition sondern eine Sammlung an möglicherweise Unterstützten Schnittstellen und man muss nur eine Teilmenge dessen (uU eine leere Teilmenge) unterstützen.

    Der Fehler liegt also nicht beim Lib Entwickler sondern in der nachträglichen Änderung.

    Man sollte dieses Tolle Features deshalb auch nutzen -> indem man erweiterte FUnktionalität bereitstellt wenn der Clientcode erweiterte Interfaces implementiert.

    Concepts sind nur für 2 Sachen gut:

    1. mehr schnittstellen zu unterstützen indem wir Member eines Typs umbenennen können
    2. schönere Fehlermeldungen zu prodzieren.

    Wenn aber .bar() ein optionales Feature ist - was definitiv der Fall ist hier (wenn auch nur zum Zeitpunkt der Library Erstellung - wenn sich Anforderungen ändern, dann ist das halt so - ist bei dynamischer Polymorphie exakt das selbe Problem).

    Ein hier im Forum bekanntes Praxisbeispiel ist ja der Einsatz von STL-Containern mit "nicht kopierbaren" Klassen, was laaaange gut gehen kann ... bis man sich plötzlich in einer totalen Sackgasse wiederfindet und seinen Code komplett umstricken muss.

    Ist es nicht ein tolles Feature dass ich nicht kopierbare Klasse in STL Container stecken kann wenn ich weiß was ich tue?

    Die C++ Philosphie ist eben Sachen zu erlauben statt sie zu verbieten. Wenn man unbedingt starke Verträge will, kann man sie einbauen - spricht nichts dagegen. Aber sie werden einem nicht aufgezwungen. Du kannst ja jederzeit auch heute schon Concept Checks verwenden wenn du es für nötig hältst. Aber der Trick ist: man muss nicht. Und dadurch ermöglichen sich viele Optionen die man mit starren Concept Checks nicht hat.

    Deswegen freue ich mich auf Concepts, da ich dort wo ich starre Verträge will bessere Fehlermeldungen erzeugen kann - aber nicht gezwungen werde starre Verträge zu verwenden.

    Sonst kommen so verrückte Sachen wie in Java <T extends Foo>
    *brr* grausig.



  • Shade Of Mine schrieb:

    ...
    Deswegen freue ich mich auf Concepts, da ich dort wo ich starre Verträge will bessere Fehlermeldungen erzeugen kann - aber nicht gezwungen werde starre Verträge zu verwenden....

    Das sagte ich ja auch.

    Kann ja sein, dass templates kein "Schnittstellenkonzept" (oder, wie ich mal gelesen habe: Kein semantisches sondern ein syntaktisches Schnittstellenkonzept) anbieten. Aber das Konzept von Schnittstellen (bzw. der "Unabhängigkeit von der Implementierung) hat einige Vorteile bzgl. Wartung/Weiterentwicklung/Reuse.

    Shade Of Mine schrieb:

    ...
    Ist es nicht ein tolles Feature dass ich nicht kopierbare Klasse in STL Container stecken kann wenn ich weiß was ich tue? ...

    Wäre ein noch tolleres Feature, wenn ich eine verbindliche Chance hätte, zu wissen, was ich tue. Wenn ich dazu die komplette Implementierung durchfräsen muss oder auf die Dokukünste eines Liberstellers angewiesen bin, finde ich das Feature weniger schön als ohne diese Anforderungen.

    Shade Of Mine schrieb:

    ...
    Wenn aber .bar() ein optionales Feature ist - was definitiv der Fall ist ...

    Tja, aber wie kann ich das dem Nutzer beibringen, was optional und was verpflichtend ist ? Bzw.: Wie soll man überhaupt ein Feature verpflichtend machen ?

    Shade Of Mine schrieb:

    ...
    Der Fehler liegt also nicht beim Lib Entwickler sondern in der nachträglichen Änderung. ...

    Ich sprach ja von einer nachträglichen Änderung durch den Lib-Entwickler. Wenn ein Lib-Entwickler auf alle Ewigkeit auf solche "impliziten Schnittstellen" festgenagelt ist (und nicht einmal feststellen kann, wann er gegen diese verstößt), finde ich das verbesserungsfähig.

    Letzten Endes läuft es genau darauf hinaus, dass man entweder den Nutzer oder den Schreiber (ab der 2.Iteration) einer Template-Lib einschränkt.
    (immerhin kann man mit Traits da noch einiges reißen - aber das hat eben wieder mal Einschränkungen für den Nutzer).
    Ist eben eine Abwägungssache.

    Nochmal: Diese "Ignoranz bei Nichtnutzung" ist ein Feature, mit dem man viele tolle Sachen machen kann ... aber IMHO auch hier und da in blöde Situationen kommt (und zwar nicht nur durch Dämlichkeit oder "an der Idee vorbeientwickeln, wie es bei jedem Sprachfeature jeder Sprache ist).

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Tja, aber wie kann ich das dem Nutzer beibringen, was optional und was verpflichtend ist ? Bzw.: Wie soll man überhaupt ein Feature verpflichtend machen ?

    das macht der Compiler für dich wenn du zu faul bist eine Doku zu schreiben.

    Ist genau wie in der STL. vector<bool> ist kein echter vector und verhält sich etwas anders. das kann man in der doku nachlesen oder anhand etwaiger fehlermeldungen feststellen.

    Ich sprach ja von einer nachträglichen Änderung durch den Lib-Entwickler. Wenn ein Lib-Entwickler auf alle Ewigkeit auf solche "impliziten Schnittstellen" festgenagelt ist (und nicht einmal feststellen kann, wann er gegen diese verstößt), finde ich das verbesserungsfähig.

    Er kann es feststellen wenn er will. Er hat die Möglichkeit Concept Checks zu verwenden. Er kann aber er wollte nicht.

    Nochmal: Diese "Ignoranz bei Nichtnutzung" ist ein Feature, mit dem man viele tolle Sachen machen kann ... aber IMHO auch hier und da in blöde Situationen kommt (und zwar nicht nur durch Dämlichkeit oder "an der Idee vorbeientwickeln, wie es bei jedem Sprachfeature jeder Sprache ist).

    Du bekommst eine klarer Compiler Fehlermeldung.
    Wie sieht es dagegen bei sagen wir einmal dynamischer polymorphie aus? Wenn ich eine nicht virtuelle funktion plötzlich virtuell mache, oder in einer basisklasse eine procteted virtuelle funktion hinzufüge, etc. das alles kann den code ebenfalls kaputt machen nur OHNE fehlermeldung.

    Und was du komplett ignorierst: ich kann auch heute schon concept checks einbauen wenn ich starke verträge will. ich kann es tun wenn ich will. Ignorier den Punkt bitte nicht.



  • Shade Of Mine schrieb:

    Simon2 schrieb:

    Tja, aber wie kann ich das dem Nutzer beibringen, was optional und was verpflichtend ist ? Bzw.: Wie soll man überhaupt ein Feature verpflichtend machen ?

    das macht der Compiler für dich wenn du zu faul bist eine Doku zu schreiben....

    Wie das ? 😕

    template <typename T>
    struct myClass {
       T t;
       void foo() { t.optional(); }
       void bar() { t.mandantory(); }
    };
    

    Wie kann ich den Compiler dazu bringen, zu überprüfen, ob T::mandantory() garantiert existiert - selbst wenn der Aufrufer bar() nicht aufruft ?

    Shade Of Mine schrieb:

    ...ich kann auch heute schon concept checks einbauen wenn ich starke verträge will. ich kann es tun wenn ich will. Ignorier den Punkt bitte nicht.

    Will ich auch nicht - aber anscheinend weiß ich nicht, was Du damit meinst / kenne "concept checks" nicht.
    (vermutlich handelt es sich dabei aber um einen "Programmiertrick" ;), der nicht direkt Teil der Sprache ist - so wie die Konvention "kein virtueller Dtor => keine Ableitung (mit "Dtor-Notwendigkeit")").
    Wie sagt ein Arbeitskollege so schön: "Warum 'Boswilligkeit' unterstellen, wo es 'Dummheit' genauso gut erklärt?" 😃

    Gruß,

    Simon2.



  • So in der Art könnte mans vielleicht machen:

    //testmand::test gibt ein "two" zurück wenn ein Methodenpointer übergeben wird
    //alles andere gibt ein char zurück
    //welche Funktion ausgewaehlt wuerde wird zur Compilezeit ermittelt
    
    struct testmand {
      struct two {char c[2]}; //deklaration reicht
    
      template <class E, class C>
      static two test(E (C::*)() ); 
    
      template <class E, class C, class A1>
      static two test(E (C::*)(A1) );
    
      static char test(...);
    }
    
    template<class T>
    class TShouldHaveMandatoryMethod {
      //wenn T kein member mit Namen mandatory hat gibts einen Compilezeitfehler
      //ist &T::mandatory kein methodenpointer, gibt test ein char zurück -> sizeof == 1, HasIt == 0
      //falls doch, gibt test ein two zurück -> HasIt == 1
    
      enum { HasIt = ( 1 != sizeof(testmand::test(&T::mandatory)) ) ? 1 : 0};
    };
    
    class With {
      int mandatory();
    };
    
    class NotReally {
      char mandatory; //heisst zwar so, ist aber keine methode
    };
    
    int main()
    {
      cout << TShouldHaveMandatoryMethod<With>::HasIt << endl; //1
      cout << TShouldHaveMandatoryMethod<NotReally>::HasIt << endl; //0
      cout << TShouldHaveMandatoryMethod<int>::HasIt << endl; //Compilezeit Fehler
    }
    

    Leider ungetestet... Das Ganze noch etwas verfeinert und ausnahmsweise mal in Makros gepackt und schon sollte es gehn. Würd mich nicht wundern wenns was in der Art in BOOST oder Loki gibt...



  • /edit: Doppelpost dank Forenlag 😞

    Wenn T::mandatory kein void zurückliefern soll, kann mans natürlich ncoh viel einfacher testen:

    template <class T>
    class NonVoidMandatoryMethod
    {
      enum { dummy = sizeof(T.mandatory()) }; //compiletime-error wenn mandatory nicht existiert oder bei sizeof(void)
    };
    


  • pumuckl schrieb:

    Leider ungetestet... Das Ganze noch etwas verfeinert und ausnahmsweise mal in Makros gepackt und schon sollte es gehn. Würd mich nicht wundern wenns was in der Art in BOOST oder Loki gibt...

    Natürlich: http://www.boost.org/doc/libs/1_36_0/libs/concept_check/concept_check.htm


Anmelden zum Antworten