Partielle Template-Spezialisierung



  • Verflixt, hab doch glatt die Fehlermeldung unterschlagen. Die hatte ich noch in der Zwischenablage. War wohl doch schon ein bisschen früh...

    Der Fehler:

    ../src/print_container.hpp:35: error: wrong number of template arguments (1, should be 4)
    stl_map.h:92: error: provided for `template<_Key, _Tp, _Compare, _Alloc> map'
    ../src/print_container.hpp:35: error: template argument 2 is invalid

    Zeile 35 entspricht Zeile 2 im letzten Codeausschnitt.

    @jencas: Verbesser mich, falls ich falsch liegen sollte, aber ich glaube verstanden zu haben, was das grundsätzliche Problem ist... Dass der Funktor nur instanziiert werden kann, wenn der Elementtyp des Containers nicht auch wieder von Template-Parametern abhängt. Also, funktioniert ein vector<int> bspw. problemlos, weil der Funktor hier mit dem Containertyp und Elementtyp instanziiert werden kann. Wenn der Elementtyp jedoch ein Paar ist, wie bei map<pair<string, int>>, ist dieses Paar ja praktisch auch wieder ein eigener Container, dessen Elementtyp man dann auch an den Funktor übergeben müsste.

    Soweit richtig?



  • moagnus schrieb:

    map<pair<string, int>>

    http://www.cplusplus.com/reference/stl/map/
    -->

    template < class Key, class T, class Compare = less<Key>,
               class Allocator = allocator<pair<const Key,T> > > class map;
    

    😉



  • Ok, dass ein map<pair<..., ...>> nicht funktioniert hab ich jetzt kapiert.
    Aber was ich nicht kapiere:
    Wenn ich mir eine eigene Funktion schreiben will, die in for_each für jedes Element aufgerufen wird, dann muss die Funktion einen Parameter vom Typ der Elemente nehmen, oder? Also bei for_each für vector<int> ist die Signatur "void f(int i)". Aber wie sieht sowas bei map aus? Müsste die Funktion nicht auch einfach map::value_type als Parametertyp nehmen, was dann doch pair<Key, T> wäre?



  • Ja, value_type gibt den Elementtypen an. Das stimmt schon, aber du musst dein Template richtig spezialisieren (also nicht mit std::map<ElemType> ). Alternativ kannst du auch den operator<< für std::pair überladen.



  • So in etwa sähe das von dir beabsichtigte aus:

    template<typename Key_Type, typename Mapped_Type, typename Compare,  typename Allocator>
    struct print_functor<typename std::map<Key_Type, Mapped_Type, Compare, Allocator>::value_type,  std::map<Key_Type, Mapped_Type> > 
       : public std::unary_function<typename std::map<Key_Type, Mapped_Type, , Allocator>::value_type, void>
    {
    private: 
      typedef typename std::map<Key_Type, Mapped_Type, Compare, Allocator>::value_type value_type;
    public:
        void operator()(const value_type& el)
        {
            std::cout << el.first << "\t\t" << el.second << '\n';       
        }
    };
    

    Allerdings gibts eines zu beachten:
    Der Standard schreibt nicht vor, dass std::map nur diese vier Template-Parameter hat. Sollte es noch mehr geben, dann funktioniert diese Spezialisierung nicht. Diese implementierung ist also nicht unbedingt portabel.


  • Administrator

    template<typename ContainerT> // Container reicht bei weitem
    struct print_functor
        : public std::unary_function<typename ContainerT::value_type, void>
    {
        void operator()(const typename ContainerT::value_type& el)
        {
            std::cout << el << '\n';
        }
    };
    
    template<typename K, typename V, typename C, typename A>
    struct print_functor<std::map<K, V, C, A> >
        : public std::unary_function<std::pair<K const, V>, void>
    {
        void operator ()(std::pair<K const, V> const& el)
        {
            std::cout << el.first << "\t\t" << el.second << '\n';
        }
    };
    
    // ...
    template<typename ContainerT>
    void print_container(const ContainerT& container)
    {
        print_functor<ContainerT> a_print_functor;
        std::for_each(container.begin(), container.end(), a_print_functor);
    }
    

    Dies dürfte eigentlich reichen und funktionieren. Auch können hier std::map verarbeitet werden, welche einen anderen Vergleich- oder Allokatortyp haben.

    Allerdings ist es nicht 100% portabel, da der Standard nicht garantiert, dass std::map nur 4 Templateparameter hat. Bei den meisten Standardbibliotheken ist dies allerdings so. Mir ist nicht mal eine bekannt, wo dies nicht der Fall ist.

    Edit: Da war pumuckl zum Teil etwas schneller 🙂

    Grüssli



  • Tausend Dank euch, Jungs! 👍

    Das Problem, vor dem ich jetzt aber stehe, ist die Frage, wie ich den Funktor instanziieren soll.
    Am liebsten wäre mir eine einheitliche Schnittstelle, d. h., print_container() soll für alle Container gleich aufgerufen werden können. Weil sich der Funktionsrumpf von print_container() abhängig vom Container aber unterscheidet, gibts dann Mehrfachdefinitionen.

    Vermutlich stell ich mich da einfach nur extrem ungeschickt an; ich hantiere aber erst seit kürzerem mit Templates und finde das teilweise noch etwas verzwickt.



  • Wo steht denn, dass eine map nicht genau 4 template-parameter haben muss?

    C++ Standard - 23.3.1 Template class map schrieb:

    • 1 A map is a kind of associative container that supports unique keys (contains at most one of each key value) and provides for fast retrieval of values of another type T based on the keys. Map supports bidirectional iterators.
    • 2 A map satisfies all of the requirements of a container and of a reversible container (23.1) and of an associative container (23.1.2). A map also provides most operations described in (23.1.2) for unique keys. This means that a map supports the a_uniq operations in (23.1.2) but not the a_eq operations.
      For a map<Key,T> the key_type is Key and the value_type is pair<const Key,T>. Descriptions are provided here only for operations on map that are not described in one of those tables or for operations where there is additional semantic information.
    namespace std {
      template <
          class Key,
          class T,
          class Compare = less<Key>,
          class Allocator = allocator<
              pair<const Key,T>
          >
      > class map;
    

    Woraus schließt ihr diese Aussage also?

    bb


  • Administrator

    @moagnus,
    Verstehe dein Problem nicht. Du kannst das Funktionstemplate print_container nehmen, welches ich im Code hingeschrieben habe. Da gibt es keine Mehrfachdefinition, ausser du machst etwas gehörig falsch, so falsch, dass ich es mir gar nicht vorstellen kann und daher keine Idee habe, was du falsch gemacht haben könntest 🙂

    @unskilled,
    Wir schliessen es aus der einfachen Tatsache, dass nirgends im Standard steht, dass std::map nicht auch noch zusätzliche Templateparemeter mit Defaultparametern haben darf. Es steht eben nichts drin 😉

    Grüssli



  • imho ist der aufbau damit explizit gegeben - aber ok, wenn das sonst drin stehen müsste, habt ihr wohl recht ^^

    bb



  • Dravere schrieb:

    @unskilled,
    Wir schliessen es aus der einfachen Tatsache, dass nirgends im Standard steht, dass std::map nicht auch noch zusätzliche Templateparemeter mit Defaultparametern haben darf. Es steht eben nichts drin 😉

    Das sehe ich nicht so. Wenn dort die Deklaration des Templates unmissverständlich mit

    template<class Key, class T, class Compare = less<Key>,
             class Allocator = allocator<pair<const Key, T> > >
      class map;
    

    beschrieben wird, gehe ich davon aus, dass eine Implementierung sich daran zu halten hat. Die "as-if"-Regel wäre bei anderen Deklarationen, die weitere Default-Parameter besitzen, auch verletzt.

    Gruß,
    SP



  • Als Alternative zu den Funktoren, bei denen bereits der Typ drei Zeilen Code braucht:

    template <typename T>
    void output_element(const T& value)
    {
    	std::cout << value << std::endl;
    }
    
    template <typename First, typename Second>
    void output_element(const std::pair<First, Second>& pair)
    {
    	std::cout << pair.first << " " << pair.second << std::endl;
    }
    
    template <typename Container>
    void output_container(const Container& c)
    {
    	for (Container::const_iterator iter = c.begin(), end = c.end(); iter != end; ++iter)
    	{
    		output_element(*iter);
    	}
    }
    

    Oder wie gesagt den operator<< für std::pair überladen (vielleicht braucht man das ja gerade sonst irgendwo), dann reicht eine Element-Ausgabefunktion und man kann mit std::for_each() arbeiten. Hey, jeweils nur eine Zeile pro Funktion, und keine Verschmutzung durch etliche typename s. 😉

    template <typename First, typename Second>
    std::ostream& operator<< (std::ostream& stream, const std::pair<First, Second>& pair)
    {
    	return stream << pair.first << " " << pair.second;
    }
    
    template <typename T>
    void output_element(const T& value)
    {
    	std::cout << value << std::endl;
    }
    
    template <typename Container>
    void output_container(const Container& c)
    {
    	std::for_each(c.begin(), c.end(), output_element<Container::value_type>);
    }
    

  • Administrator

    @Sebastian Pizer,
    Lies mal Kapitel 1.4 (Implementation compliance) des Standards. Dort steht, dass diese Beispiele nur teilweise Definitionen sind und dass eine Bibliothek diese Beispielimplementationen auch erweitern darf, sofern das Programm durch die Veränderungen mit der gezeigen Form im Standard trotzdem korrekt ist. Deshalb müssen die zusätzlichen Templateparameter auch Defaultparameter haben, sonst würden sie dagegen verstossen.

    Edit: Kapitelname noch eingefügt.

    Grüssli



  • Dravere schrieb:

    ... Deshalb müssen die zusätzlichen Templateparameter auch Defaultparameter haben, sonst würden sie dagegen verstossen.

    "dagegen"? Wogegen denn genau?
    Ich kann Kapitel 1.4 nichts entnehmen, was mit Deiner Interpretation vereinbar ist.

    Bzgl Klassen und Klassen-Templates steht da

    For classes and class templates, the library clauses specify partial definitions. Private members (clause 11) are not specified, but each implementation shall supply them to complete the definitions according to the description in the library clauses.

    Dass dort keine kompletten Definitionen spezifiziert sind, ist klar. Aber wenn ich eine Deklaration eines Klassentemplates ohne irgendwelche in kursiv gesetzte Platzhalter oder Bemerkungen sehe, ist das für mich eine Deklaration, an der nicht gerüttelt werden darf.

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    [...]wenn ich eine Deklaration eines Klassentemplates ohne irgendwelche in kursiv gesetzte Platzhalter oder Bemerkungen sehe, ist das für mich eine Deklaration, an der nicht gerüttelt werden darf.

    So seh ich das auch...


  • Administrator

    @Sebastian Pizer,
    Was du machst ist eine eigenständige Interpretation, es steht aber nicht so im Standard und es müsste explizit stehen, damit es so ist. Denn im Standard wird alles genau festgelegt. Um deine Behauptung also zu beweisen, musst du mir im Standard zeigen, wo steht, dass die Implementation sich 1:1 an diese Codebeispiele zu halten haben. Vor allem in diesem Punkt in Bezug auf Templateparametern.

    Zusätzlich steht übrigens auch im Kapitel 1.4:

    A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this International Standard. Having done so, however, they can compile and execute such programs.

    Es dürfen Erweiterungen existieren. Das Programm muss einfach nur well-formed bleiben. Es steht im Standard, dass std::map 4 Templateparameter haben soll. Man muss also schreiben dürfen: std::map<A, B, C, D> .
    Dies verbietet allerdings nicht eine solche Deklaration:

    template
    <
      typename A,
      typename B,
      typename C = std::less<A>,
      typename D = std::allocator<std::pair<A const, B> >,
      typename X = int /*,
      ... */
    >
    class map
    {
      // ...
    };
    

    Grüssli



  • Dravere schrieb:

    Es dürfen Erweiterungen existieren. Das Programm muss einfach nur well-formed bleiben. Es steht im Standard, dass std::map 4 Templateparameter haben soll. Man muss also schreiben dürfen: std::map<A, B, C, D> .

    Sagt wer?
    Ist das hier auch well-formed?

    #include <map>
    template<template<class,class,class,class> class Foo>
    struct blupp {};
    int main()
    {
      blupp<std::map> x;
    }
    

    Wo ziehst Du denn da die Grenze?

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Dravere schrieb:

    Es dürfen Erweiterungen existieren. Das Programm muss einfach nur well-formed bleiben. Es steht im Standard, dass std::map 4 Templateparameter haben soll. Man muss also schreiben dürfen: std::map<A, B, C, D> .

    Sagt wer?
    Ist das hier auch well-formed?

    #include <map>
    template<template<class,class,class,class> class Foo>
    struct blupp {};
    int main()
    {
      blupp<std::map> x;
    }
    

    Opops.Mein erstes Statement zu dem Thema war falsch. Natürlich kann man die Geschichte mit der map<X, Y> bzw. map<X, Y, C, A> machen - das ist kein Problem. Anders ists mit dem template template Parameter den du hier gezeigt hast. Ich weiß nicht mehr obs Meyers in einem der Effective C++ Bücher oder Sutter in einem der Exceptional C++ Bücher war - aber genau dieses Beispiel taucht dort auf. Eben weil die Standardcontainer zusätzliche (per default gesetzte) Templateparameter haben dürfen kann es passieren, dass der Compiler std::map nicht mit einem 4-Parameter-Template matchen kann.


  • Administrator

    @Sebastian Pizer,
    Es gibt nirgendwo eine Garantie, dass std::map genau 4 Templateparameter hat, nur dass man ganz sicher 4 angeben kann. Das ist ein Unterschied. Deshalb ist dein Programm einfach nicht Standardkonform.

    Sebastian Pizer schrieb:

    Wo ziehst Du denn da die Grenze?

    Die Grenze zieht der Standard und zum Teil ist keine vorhanden.

    @pumuckl,
    Bist du sicher dass es funktioniert? Ich hatte irgendwas im Kopf, dass es unter speziellen Umständen nicht funktionieren kann. Shade Of Mine hat da mal vor langer Zeit etwas präsentiert gehabt. Kann mich nur nicht mehr richtig erinnern und finde es aktuell auch nicht mehr...
    Deshalb habe ich, um meine Aussage abzusichern, den Satz noch dazugeschrieben 🙂

    Grüssli



  • Dravere schrieb:

    @Sebastian Pizer,
    Es gibt nirgendwo eine Garantie, dass std::map genau 4 Templateparameter hat, nur dass man ganz sicher 4 angeben kann. Das ist ein Unterschied. Deshalb ist dein Programm einfach nicht Standardkonform.

    Wieso ist es das nicht? Woran machst Du das fest? Welcher Satz aus dem Standard lässt Dich das glauben? Du ziehst willkürlich eine Grenze zwischen map<int,int,comp,my_alloc> muss funktionieren aber als std::map muss nicht als template<class,class,class,class>class-Parameter benutzbar sein.

    So, wie ich das sehe, beziehen sich die Zitate, die Du gebracht hast, auf Definitionen (Räpresentierung ist implementierungsabhängig), und nicht die Deklarationen.

    Gruß,
    SP


Anmelden zum Antworten