"Mitgebrachte" Includes aus Header vermeiden



  • Huhu,

    Ich habe vor 2 Wochen ein Video über einen sog. "Pretty printer" gesehen, der alle Arten von Standardcontainern und Typen ausgeben kann und noch dazu generisch und erweiterbar ist. Nun hab ich aus dem Kopf heraus versucht, etwas ähnliches zu konstruieren.

    Allerdings sind hier viele Spezialisierungen nötig, damit einzelne Container wie vector, map, set richtig ausgegeben werden. Dafür muss ich alle Header includen, was dann den Nachteil hat, dass jemand, der meinen Printer includet, gleich mal alle Container mit hinterhergeschmissen bekommt. Wie kann ich das vermeiden? Am besten wäre, wenn alles Header-only bleiben könnte.

    Falls es wichtig ist, hier auch noch der Code (Da sehr lang mit Absicht nach Ideone gepastet.): http://ideone.com/zbCvn

    Grüße,
    PI



  • Wenn du für einzelne Containertypen gesonderte Prä- und Suffixe festlegen willst, ohne dass der Benutzer dafür eine eigene Policy angeben muss, musst du zumindest die Container einbinden, für die du gesondert spezialisieren willst. Ansonsten riecht das nach SFINAE. Ich stelle mir das etwa so vor (der Einfachheit halber erstmal ohne Policies):

    #include <iostream>
    #include <string>
    #include <utility>
    #include <boost/utility/enable_if.hpp>
    
    typedef char true_type;
    struct false_type { char a[2]; };
    
    // Containererkennung
    template<typename T> true_type  has_iterator(typename T::iterator const *);
    template<typename T> false_type has_iterator(...);
    template<typename T>
    struct is_container { static bool const value = sizeof(has_iterator<T>(0)) == sizeof(true_type); };
    
    template<typename T> typename boost::disable_if<is_container<T>, void>::type print(T                    const &t, std::ostream &out = std::cout);
    template<typename T> typename boost:: enable_if<is_container<T>, void>::type print(T                    const &c, std::ostream &out = std::cout);
    template<typename T>                                             void        print(std::basic_string<T> const &s, std::ostream &out = std::cout);
    template<typename iter_t>                                        void        print(iter_t first, iter_t last,     std::ostream &out = std::cout);
    template<typename T, typename U>                                 void        print(std::pair<T, U>      const &p, std::ostream &out = std::cout);
    
    template<typename T>
    typename boost::disable_if<is_container<T>, void>::type
    print(T const &t, std::ostream &out) { out << t; }
    
    template<typename T>
    typename boost::enable_if<is_container<T>, void>::type
    print(T const &c, std::ostream &out) {
      print(c.begin(), c.end(), out);
    }
    
    template<typename T, typename U>
    void print(std::pair<T, U>      const &p, std::ostream &out) {
      out << "( ";
      print(p.first);
      out << ", ";
      print(p.second);
      out << " )";
    }
    template<typename T>             void print(std::basic_string<T> const &s, std::ostream &out) { out << '"' << s << '"'; }
    
    template<typename iter_t>
    void print(iter_t first, iter_t last, std::ostream &out) {
      out << "{ ";
    
      if(first != last) {
        print(*first, out);
        while(++first != last) {
          out << ", ";
          print(*first, out);
        }
      }
    
      out << " }";
    }
    
    #include <map>
    #include <vector>
    
    int main() {
      std::map<int, std::vector<std::pair<int, std::string> > > m;
    
      m[1].push_back(std::make_pair(1, "foo"));
      m[1].push_back(std::make_pair(2, "bar"));
      m[1].push_back(std::make_pair(3, "baz"));
      m[2].push_back(std::make_pair(4, "qux"));
      m[2].push_back(std::make_pair(5, "quux"));
      m[3].push_back(std::make_pair(6, "xyzzy"));
    
      print(m);
      std::cout << std::endl;
    }
    

    In diesem Fall sind besondere Spezialisierungen für std::pair und std::basic_string vorhanden, deswegen müssen <utility> und <string> eingebunden werden, aber zumindest spart man sich die ganzen Container und eine Menge Code.



  • Sieht schon mal gut aus, und vor allem viel kürzer 👍

    Da ich hier ja nur mit const_iterator'en iterieren möchte, wäre hier wohl ein check auf einen solchen besser.
    boost steht nicht zur Verfügung, allerdings C++0x, wobei es ja hier wie es scheint disable_if gar nicht gibt? 😮

    Was mir dann noch einfällt: Es gibt weder bei meiner, noch bei deiner (nehme ich an, ich kenne deine erweitere Policy-Variante ja nicht.) eine Möglichkeit zu sagen:
    "Nimm bei allen Funktionen den Formatter xy". Quasi eine Möglichkeit, ein typedef zu machen.
    Allerdings scheint mir ein Klassentemplate mit lauter statischen Membern dann doch etwas seltsam.

    Wie würdest du hier die Formatierung einbauen? 😋



  • Ich hab keine Policy-Variante, sondern das nur eben zusammengehackt.

    Wenn du disable_if nicht zur Verfügung hast, kann man das über eine triviale Metafunktion zu machen:

    template<typename T> struct meta_not { static bool const value = !T::value; };
    
    ...
    
    typename std::enable_if<meta_not<bedingung<T> >, void>::type func() {}
    

    Ansonsten ist disable_if auch leicht nachzubauen.

    Was die Policies angeht, das mit dem Typedef halte ich nicht für sinnvoll. Es geht, wenn man das mit Policies betreibt, ja gerade darum, das Verhalten nicht an zentraler Stelle festzuzurren. Reich halt ein Objekt von Funktion zu Funktion.

    Was die weiteren Details angeht, so ist das eine Designfrage. Ich halte es eigentlich für übertrieben, containerspezifisch verschiedene Begrenzer einzubauen, aber wenn du es gerne so haben willst, läuft es auf eine Menge Template-Spezialisierungen hinaus, für die du die betroffenen Container jeweils kennen musst. Ob man das mit statischen Funktionsvorlagen erledigen kann, hängt davon ab, ob man dem Formatierer zur Laufzeit einen Status, der sein Verhalten beeinflusst, mitgeben will oder nicht. Es gibt da viele Herangehensweisen, die je nach Anwendungsfall mehr oder weniger Sinn ergeben. Du solltest dir, bevor du diese Entscheidung triffst, Gedanken darüber machen, was das Ding am Ende eigentlich genau können soll (und dabei YAGNI sowie KISS im Hinterkopf behalten - bau kein Enterprise-Framework, wenn du es vermeiden kannst!).



  • Das meta_not kann ich mir auch ersparen und stattdessen gleich ein ! verwenden 😉

    Ich dachte bei den Policies an sowas:

    typedef printer<my_formatter> my_printer;
    my_printer::print(my_map);
    my_printer::print(my_vector);
    ...
    

    Ich habe mich entschieden, aus Einfachheit per default bei allen Containern {} zu verwenden. Allerdings sollte man per eigenem Formatter das jederzeit ändern können. Formatter sollten auch einen Status haben können.

    Naja, was soll das können? Ich wollte einfach mal einen möglichst generischen Printer schreiben 😉
    In der Praxis werde ich das sowieso nicht einsetzen.


Anmelden zum Antworten