Garantiert ein iterator<T> Argument ADL in den Namespace von T?



  • Anders gesagt, ist garantiert dass das funktioniert?

    #include <vector>
    
    namespace Foo {
    
    struct Bar {};
    
    template <class T>
    void fun(T) {}
    
    } // namespace Foo
    
    void test() {
        std::vector<Foo::Bar> v;
        fun(v.begin()); // found via ADL - but can we rely on this?
    }
    

    Mir ist keine solche Garantie bekannt - aber mit "üblichen" Iterator-Implementierungen funktioniert es. Ist aber auch nicht sonderlich schwer std::vector::iterator so zu implementieren dass es nicht geht.


  • Mod

    Vorab eine kurze Erklaerung warum obiges ueberhaupt wohlgeformt ist: i.d.R. sind die iterator Typen Template-Spezialisierungen, und da greift

    Furthermore, if T is a class template specialization, its associated entities also include: the entities associated with the types of the template arguments provided for template type parameters; [...]

    Fuer libstdc++ bspw.

    typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator;
    

    wo gleich beide Template-Argumente den value type als associated entity haben. Es funktioniert auch wenn iterator einfach ein Zeiger ist.

    Wenn wir uns vorstellen, dass iterator einfach eine Memberklasse ist, dann ist die einzige associated entity die umschließenden Klasse, d.h. die vector Spezialisierung. Die Menge fasst nicht transitiv dessen associated entities mit ein. Haben wir den skeleton

    template<typename T>
    struct vector {
        class iterator {};  
        iterator begin();
    };
    

    dann kompiliert dein Beispiel nicht mehr.

    tl;dr: Nein, es ist nicht garantiert.



  • @Columbo
    Danke!

    Ich hätte eine weitere Frage dazu. Kennst du eine Regel die es für den Spezialfall garantiert dass alle 3 Beteiligten im Namespace std zu finden sind (also der Container, der Item-Typ und das Funktions-Template)? Also z.B.

        std::vector<std::string> vec;
        sort(vec.begin(), vec.end());
    

    In dem Fall würde es ja reichen wenn z.B. der Iterator-Typ im Namespace std ist, oder nested-Type von etwas in stdist. Nur ist mMn. laut Standard auch diesbezüglich nichts garantiert.

    Oder anders gefragt: wäre folgendes eine legale Möglichkeit einen Iterator zu definieren?

    namespace std {
    namespace __iterators {
    
    template <class T>
    struct vector_iterator {
        struct type {
            // iterator implementation
        };
    };
    
    } // namespace __iterators
    
    template <class T, ...>
    class vector {
        using iterator = ::std::__iterators::vector_iterator<T>::type;
    };
    
    } // namespace std
    

    In dem Fall wäre ja der einzige Namespace der über ADL gefunden wird ::std::__iterators und selbst das Beispiel oben würde nicht funktionieren.


  • Mod

    Witzig, ich hatte genau diesbezueglich ein LWG issue gesehen, als ich deine urspruengliche Frage recherchiert hatte: https://wg21.cmeerw.net/lwg/issue2269
    Das Thema ist nicht abschliessend geklaert. Diese gesamte Problematik ist underspecified.



  • Also schätze dass diesbezüglich gar nix garantiert ist, und es nur per Zufall funktioniert.
    Komisch dass weder libstdc++ noch libc++ Iteratoren so implementieren dass man per ADL nur in einen kleinen Detail Namespace kommt, wo nix interessantes zu finden ist.
    Würde ja helfen portability issues frühzeitig zu erkennen.



  • @hustbaer @Columbo Manchmal ist es vielleicht ein Segen, wenn man nicht so tief in C++ drinsteckt, dass man überhaupt auf die Idee kommt, dass sowas funktionieren könnte. Ich kenne zwar ADL und weiss, was hier passiert, aber ich wäre beim Programmieren nie auf die Idee gekommen, das in dieser Form zu schreiben. Bei mir wäre das entweder Foo::fun(v.begin()) oder bestenfalls fun(*v.begin()), wenn schon mit ADL. Ich wusste gar nicht, dass es überhaupt Iteratoren gibt, mit denen der Namespace auf diese Art "über Bande" gefunden wird.

    Insofern ist Ignoranz nicht immer eine schlechte Sache und schützt mich hier vor solchen subtilen Fehlern/Compilerinkompatibilitätsproblemen 😉


  • Mod

    @Finnegan Ich vermute, dass die meisten Vorkommnisse unabsichtlich verfasst werden. Weil man irgendwo std aufmacht und anderswo zufälligerweise nicht. Und dann spielt es keine Rolle mehr, ob man ADL in diesem Zusammenhang gegenueber ignorant ist. Das Problem besteht darin, dass das Konstrukt zufaellig wohlgeformt sein kann, aber nicht garantiert; der Standard sollte schon eindeutig vorgeben, ob es immer oder nie wohlgeformt ist.



  • Ich vermute auch dass das dort wo ich es gesehen habe per Zufall entstanden ist.
    Wir haben an einigen Stellen using namespace std; im Code. Dabei waren auch einige Stellen in Header Files - was wir aber vor einiger Zeit grösstenteils aufgeräumt haben. Kann leicht sein dass das dabei entstanden ist: using namespace std; entfernen, bauen und dann alle Fehler fixen. Und da an der Stelle eben kein Fehler gekommen ist...



  • @Columbo @hustbaer Ja, das ist nachvollziehbar. Hatte mir dein 2. Codebeispiel nicht so richtig angesehen. Dass z.B. so ein sort(irgendwas) auch ohne irgendein using potentiell std::sort finden kann, könnte schon für unangenehme Überraschungen sorgen - auch wenn mir das bisher zum Glück noch nicht untergekommen ist. Besonders wenn man im eigenen Code auch noch ein sort herumfliegen hat, das man eigentlich gemeint hat.

    Gerade auch weil ich viel mit Namespaces arbeite haben viele meiner Funktionen so kurze und simple Namen, die sich auch im std-Namespace finden - auch wenn die Funktionen manchmal etwas ganz anderes machen.

    Also sehe ich das richtig, dass die bevorzugte Garantie wäre, dass die Namespaces eben nicht über den Umweg eines Iterators gefunden werden sollen? Das wäre meine Präferenz. Dein Eingangsposting las sich nämlich eher so, als hättest du dieses überraschende Verhalten gerne garantiert. Ich denke das war so nicht gemeint (?).



  • @Finnegan sagte in Garantiert ein iterator<T> Argument ADL in den Namespace von T?:

    Also sehe ich das richtig, dass die bevorzugte Garantie wäre, dass die Namespaces eben nicht über den Umweg eines Iterators gefunden werden sollen?

    Ja, ich denke das wäre besser.

    Das wäre meine Präferenz. Dein Eingangsposting las sich nämlich eher so, als hättest du dieses überraschende Verhalten gerne garantiert. Ich denke das war so nicht gemeint (?).

    Ich wollte primär einfach wissen ob es da eine Garantie gibt die ich nicht kenne. Damit ich nicht Arbeitskollegen sage "das ist falsch", wenn es in Wirklichkeit gar nicht falsch ist 🙂


Anmelden zum Antworten