ADL und Stream operator<<



  • Hallo,

    ich google inzwischen seit Stunden und komme einfach nicht auf einen grünen Zweig, deswegen bitte ich um Hilfe. Ich weiß nichtmal wie ich die Frage "simpel" stellen soll 😃

    Ziel:
    Ich möchte zum Debuggen/Loggen ein Objekt ausgeben.

    //main.cpp
    std::cout << controller << std::endl;
    

    // Implementiert als friend operator<< soll eine boost::unordered::unordered_map ausgegeben werden.

    template<class E, class T, class K >
    class DefaultController
    {
    // ...
    public:
        friend ostream &operator<<(
            ostream &os,
            const DefaultController<E, T, K> &dc)
        {
            // Nächste Zeile funktioniert nicht, da operator<<(ostream &, const unordered_map &) in einem anderen namespace (ein Art Utils namespace) definiert wurde
            // os << dc.get_callbacks() << std::endl;
    
            // okay akzeptiert
            return Raccc::operator<<(os, dc.get_callbacks()) << std::endl;
        }
    // ...
    }
    

    Ich gehe davon aus, dass bei der HashMap der Key und der gespeicherte Typ einfach weiter mit dem "<<" Operator ausgegeben werden kann.
    Nachdem ich in meinem Fall allerdings ein Set speichere, habe ich darunter dazu die Implementierung zu einem Set dazu gemacht.

    namespace Raccc {
    using std::ostream;
    
    // hash map
    template<class T1, class T2, class T3, class T4, class T5>
    ostream &operator<<(
        ostream &os,
        const boost::unordered::unordered_map<T1,T2,T3,T4,T5> &um)
    {
        typedef typename boost::unordered::unordered_map<T1,T2,T3,T4,T5> Um;
    
        for(typename Um::const_iterator
            it = um.begin(),
            end = um.end();
            it != end; ++it) {
                const typename Um::value_type &p = *it;
                os << "H[" << p.first << "] = " << p.second << std::endl; // ERROR !!!!!!!!
        }
    
        return os;
    }
    
    // set
    template<class T1, class T2, class T3, class T4>
    ostream &operator<<(
        ostream &os,
        const boost::container::set<T1,T2,T3> &us)
    {
        typedef typename boost::container::set<T1,T2,T3> Us;
    
        for(typename Us::const_iterator
            it = us.begin(),
            end = us.end();
            it != end; ++it) {
                const typename Us::value_type &v = *it;
                os << "(" << v << ")";
        }
    
        return os;
    }
    }
    

    Nun weiß der Compiler nicht, was er mit p.first (=set) machen soll, weil er die Implementierung unten nicht findet. Explizit kann ich ihm aber nicht Raccc.operator<< angeben, weil es ja sein kann, das p.first ein int ist und das in std definiert wurde. Also wie könnte ich das lösen?

    // add: Ich wollte noch anmerken, dass ich natürlich die geeigneten Header Dateien inkludiere und alle Implementierungen hier auch in Header Dateien sind.
    Ich habe gerade auch noch einen Typo entfernt. Es ist weitaus mehr Code, der allerdings nicht relevant sein dürfte.

    Vielen Dank
    gruß Philipp



  • ADL sucht in den Namespaces wo der Argumenttyp definiert wurde.
    Im deinem Fall sucht er in std (ostream) und boost::container (set). In keinem gibt es aber wohl einen operator<<. Du müsstest den operator<< entweder in std oder in boost::container definieren, damit ADL greift; beides darfst du aber nicht.
    Die einzige Lösung ist es dem Compiler funktionslokal zu sagen, dass er auch im namespace Racc suchen soll, das machst du mit einem using namespace Racc bzw. using Racc::operator<<. Ist keine elegante Lösung, aber eigentlich sollte man auch nicht so Typen erweitern.



  • Wie löse ich das Problem denn dann am besten? Ich finde es selber nicht optimal so. Würde ich alles im globalen, oder gleichen Namespace laufen lassen, hätte ich wohl auch keine Probleme. Ich halte die Lösung, dass es ein komplett generische Implementierung für die Ausgabe von ADTs gibt für nicht so schlecht. Jeder Entwickler wäre somit einfach selber zuständig dafür dass er für seine Klassen den "<<" Operator implementiert, falls die Ausgabe erwünscht ist.



  • PhilippHToner schrieb:

    Wie löse ich das Problem denn dann am besten? Ich finde es selber nicht optimal so. Würde ich alles im globalen, oder gleichen Namespace laufen lassen, hätte ich wohl auch keine Probleme.

    Bei der Implementierung des DefaultController operator<< nicht einen anderen operator<< aufrufen, sondern manuell den Code einfügen, der ausgibt.

    Ich halte die Lösung, dass es ein komplett generische Implementierung für die Ausgabe von ADTs gibt für nicht so schlecht. Jeder Entwickler wäre somit einfach selber zuständig dafür dass er für seine Klassen den "<<" Operator implementiert, falls die Ausgabe erwünscht ist.

    So ist es doch. Jeder Entwickler ist selber dafür zuständig den operator<< zu implementieren, wenn Ausgabe erwünscht ist. Bei Containern ist sie offenbar nicht erwünscht.



  • Würde ich die Logik, eine Hashmap auszugeben, im DefaultController implementieren, kann ich diesen Code in jede neue Klasse kopieren, sollte diese auch eine Hashmap ausgeben. Außerdem will ich noch boost::container::list<E> ausgeben und das will ich auch nicht überall implementieren.
    Gibt es denn nicht die Möglichkeit das einmalig zu definieren?



  • PhilippHToner schrieb:

    Gibt es denn nicht die Möglichkeit das einmalig zu definieren?

    Doch. Wie ich sagte namespace Racc via using zu einem Namespace machen, wo ebenfalls drin gesucht wird.


Anmelden zum Antworten