Compiletime Schnittstellen



  • Er will so etwas wie pimpl nur ohne pimpl.



  • Argh, wo ich in meinem Posting Contraint geschrieben habe, meinte ich natürlich Concept.



  • Hier mal Pseudocode

    static_interface some_interface {
       int a( bool b );
       void c( float d);
    };
    
    class impl1 : some_interface {
       int a( bool b );
       void c( float d);
    };
    
    class impl2 : some_interface {
    };   // whoah, kompiliert nicht, weil impl2 nicht drop-in-kompatibel zu some_interface ist!
    

    Also eigentlich soll soetwas wie typedef-kompatibilität gesichert werden. Wo immer impl1 verwendet wird, könnte per typedef auch impl2 verwendet werden und der Compiler warnt den Entwickler, wenn er gegen die Kompatibilität verstößt.


  • Mod

    pure virtual functions?



  • @SeppJ Prinzipiell soetwas in der Art, aber die Checks ganze soll zur Compile-Zeit ablaufen, weil die Typen bekannt sind. Also anderes Beispiel: Das C++ Standardkommitee gibt für die komplette STL so eine static_interface-Beschreibung raus und alle STL-Implementierer leiten ihre Implementierungen davon ab und der Compiler würde sie daran erinnern, wenn sie irgendwas vergessen. Mag jetzt ein schlechtes Beispiel sein 😉



  • Also suchst du doch Concepts: http://www.boost.org/libs/concept_check/

    template <typename T>
    bool static_interface()
    {
      T t; // Defaultkonstruktor                                                             
      int a = t.a((bool)true); (void)a; // int a(bool)                                       
      t.c((float)0);  // void c(float)                                                       
      return true;
    }
    bool _i_m_p_l_1_ = static_interface<impl1>(); // ok
    bool _i_m_p_l_2_ = static_interface<impl2>(); // Fehler
    


  • Ja, so einen Test könnte man so "emulieren" (Im Prinzip ist das ja ein "Full-Interface-Coverage"-Clientcode), aber wäre es nicht toll, wenn die Sprache das einfach so unterstützt? Bei Concepts war ich mir nicht sicher, ob die nur im Kontext von template-Argumenten funktionieren, oder ob man die auch für nicht-Template-Klassen verwenden kann.





  • Nunja, halt doch nicht ganz so irgendwie, wenn ich die concepts richtig verstehe.
    Ich dem Fall der c++-concepts ist es ja der Client-Code eines Typs, der Anforderungen an diesen Typ stellt, außerdem allem ist dieser Client-Code selber ein template.
    Ich als Bibliotheken-Schreiber möchte aber überprüft haben, dass mein Code ein bestimmtes concept einhält. Wenn ich das richtig verstehe, was ich über concepts gelesen habe, dann muss ich mir immer noch einen extra-Client schreiben, der für mich überprüft, ob ich das concept wirklich eingehalten habe. Ich möchte aber schon beim Implementieren per Konvention dieses concept einhalten (so dass der Typ gar nicht erst vom Compiler akzeptiert wird, wenn er das concept nicht erfüllt). So ein Konstrukt sehe ich da im Moment noch nicht (wobei meine Augen auch nicht mehr die besten sind!).



  • Das schöne an Concepts ist eben, dass sie auf mehrere Arten gelten. Für den, der die Funktion aufruft, für den, der sie schreibt und für den, der den Typ bereitstellt. Du suchst Concept maps (in deinem Fall concept_map static_interface<impl1> {}; . Von Boost wird das imho auch abgedeckt.



  • Also würde das das folgende bedeuten für meinen pseudo-Code?

    concept some_interface<T> {    // T unnötig?
       int a( bool b );
       void c( float d);
    };
    
    class impl1 {
       int a( bool b );
       void c( float d);
    };
    
    concept_map some_interface<impl1> {}  // OK
    
    class impl2 {
    };   
    
    concept_map some_interface<impl2> {} // Nicht OK
    

    Irgendwie erscheint mir das geistig "quer" (weil die Interface-Garantie nicht teil der Implementierung ist), da verstehe ich etwas noch nicht.



  • Ja, du willst Concepts haben. Und nach dem letzten Stand des Sprachfeatures, also, bevor es wieder rausflog, hätte man das in deinem Fall so aufschreiben müssen:

    concept some_interface<typename T>
    {
      int T::a(bool);
      void T::c(float);  
    }
    

    Das Bekloppte daran ist nur, dass diese Constraints wie Funktionsdeklarationen aussehen, aber in Wirklichkeit für Ausdrücke stehen, die "funktionieren müssen", wenn T wirklich ein some_interface ist. Also, man muss in diesem fall eine Methode a aufrufen können, wo man ein bool reinstecken kann (auch wenn es z.b. implizit zu etwas anderes konvertiert wird) und etwas zurück gibt, was man wieder nach int konvertieren kann.

    In dem Concepts-Design war es aber nicht vorgesehen, dass man explizit sagen kann, dass eine Klasse einem Konzept entsprechen soll und daraufhin überprüft werden soll. Ich sehe auch nicht den Vorteil. Da wo Concepts dann wirklich zum Tragen kommen, sind die requires-Klauseln gewesen, mit denen du dann "modular type checking" bekommt. Die Templates können daraufhin auf ihre "Korrektheit" überprüft werden, bevor sie überhaupt instantiiert. Und der Anwender eines solchen Templates bekommt auch eine Fehlermeldung, wenn er bestimmte Bedingungen nicht erfüllt. Man kann also die Schuld jemandem zuweisen, dem Template-Autor oder dem Template-Nutzer.

    template<typename T>
      requires some_interface<T>
    void foo(T & obj)
    {
      obj.a(23);
      obj.c(3.14f);
    }
    

    Abkürzungs-Syntax:

    template<some_interface T>
    void foo(T & obj)
    {
      obj.a(23);
      obj.c(3.14f);
    }
    

    Die Constraints beschränkten sich auch nicht auf Elementfunktionen, sondern umfassten auch "assoziierte Typen" sowie freie Funktionen. Beispiel:

    concept Swappable<typename T>
    {
      void swap(T&,T&);
    }
    

    oder auch

    concept PointerLike<typename T>
      : DefaultConstrucbible<T>
      , MoveConstructible<T>
      , MoveAssignable<T>
    {
      typename reference_type;   // assoziierter Typ
    
      reference_type operator*(T);    // Dereferenzierung
    }
    

    Und Concepts beschränkten sich auch nicht auf einen Parameter. Man konnte darin z.B. auch Beziehungen zwischen mehreren Typen ausdrücken:

    concept Assignable<T,U>
    {
      T& T::operator=(U);
    }
    

    Ich bin mal gespannt, was das noch gibt. Bin aber auch froh, dass das wieder aus dem C++0x Entwurf rausgeflogen ist. Das haben ja nichtmal 3% des Kommittees verstanden. Ich habe das 2-3 Jahre lang verfolgt, die Papers gelesen und versucht zu verstehen. Was für ein komplizierter Scheiß das war ... Es hat auch nicht besonders toll mit dem einen oder anderen C++11-Feature interagiert -- z.B. mit Rvalue-Referenzen.



  • Decimad schrieb:

    Irgendwie erscheint mir das geistig "quer" (weil die Interface-Garantie nicht teil der Implementierung ist), da verstehe ich etwas noch nicht.

    Muss sie das explizit angeben? Ich finde dieses Compile-Time-Duck-Typing gar nicht schlecht. Zumindest muss es "auto-Konzepte" geben. Keiner will an jeden Typen extra dranschreiben müssen, dass er kopierbar, zuweisbar, default-konstruirbar ist u.s.w. Das verrät einem die Schnittstelle ja schon. "auto concepts" sind dann solche, die sich automatisch auf alles übertragen, was "strukturell" passt. Im vorherigen Posting hätte ich z.b. mindestens bei bei Swappable und Assignable ein "auto" davor setzen müssen, weil ich nicht für jeden Typen extra per concept_map sagen will, dass man ein swap aufrufen kann oder eine Zuweisung durchführen kann. Das ergibt sich ja schon aus der Schnittstelle.

    Man unterscheidet da zwischen Bedingungen, die rein "strukturell" sind und semantischen Bedingungen. Wenn ich ein swap für ein T aufrufen kann, dann reicht mir das. Das ist der strukturelle Teil. Der semantische Teil eines solchen Constraints würde dann sagen "wenn man dieses swap ausführt, wird auch tatsächlich der Inhalt/die Werte der beiden Objekte getauscht". Das lässt sich nicht in Code aufschreiben und deswegen gab es auch die Möglichkeit Konzepte so zu definieren, dass man doch explizit sagen muss, welche Typen alle dieses Konzept modellieren.

    Ein Haken bei der ganzen Concepts-Geschichte war, dass man viel zu viel Code schreiben musste. Ich will kein "modular typechecking" haben, wenn ich dafür viel viel mehr Code schreiben muss.

    Ach, eins, was Concepts ermöglichen sollte, was wir aber noch nicht genannt haben, war "concept-based overloading", also:

    template<ForwardIterator Iter>
    void advance(Iter & iter, Iter::difference_type step)
    {
      assert(step>=0);
      while (step>0) {
        ++iter;
        --step;
      }
    }
    
    template<BidirectionalIterator Iter>
    void advance(Iter & iter, Iter::difference_type step)
    {
      while (step>0) {
        ++iter;
        --step;
      }
      while (step<0) {
        --iter;
        ++step;
      }
    }
    
    template<RandomAccessIterator Iter>
    void advance(Iter & iter, Iter::difference_type step)
    {
      iter += step;
    }
    

    Das macht man im Moment per "tag dispatching", aber dieses concept-based overloading wär natürlich ein netter Ersatz dafür.

    Also alles in allem ... die Ziele von Concepts waren hoch gesteckt. Kurzfassung: Ein Typsystem für Typen.


Anmelden zum Antworten