argument-dependent lookup + namespace



  • Nexus schrieb:

    Hm, gerade noch eine passende Frage dazu:

    Überladet ihr im Allgemeinen ein globales (bzw. im eigenen Namensraum) swap() für euren Typen und hofft darauf, dass der Anwender jeweils using std::swap; swap(a,b); schreibt, oder spezialisiert ihr das std::swap -Template und spekuliert, dass der Anwender std:: angibt? 😉

    Ich spezialisiere std::swap so dass es meine eigene swap-Routine aufruft. Damit sind alle Möglichkeiten abgedeckt. Denn leider schreibt der Anwender nicht immer using std::swap sondern ruf std::swap direkt explizit auf (selbst einige STL-Implementierungen machen das leider).



  • Ah danke, ja das scheint noch Sinn zu machen. Allerdings ist es etwas mühsam, jeweils drei Funktionen (inklusive Memberfunktion) bereitzustellen.

    Hm... Irgendwie gerate ich da in Versuchung eines bösen Makro-Hacks... :p
    Aber vorläufig werde ich es wohl direkt mit deiner Methode versuchen, allzu schlimm sind die paar Zeilen Code ja nicht. 🙂



  • swap im eigenen Namespace selbst zu definieren, ist nur dann nötig, wenn man Template-Klassen swappen will.

    Es ist nämlich erlaubt std::swap zu spezialisieren, aber nicht zusätzliche Overloads zu erzeugen.

    Da Funktions-Templates nicht partiell spezialisiert werden können, muss für Dinge wie swap(boost::shared_ptr<T>&, boost::shared_ptr<T>&) ein Overload her.
    Und da man diesen Overload wie gesagt (leider) nicht in std:: machen darf, muss er in den Namespace der shared_ptr<> definiert, damit er über ADL gefunden wird. D.h. in diesem Fall "boost".
    (Technisch geht es natürlich std::swap zu überladen, aber es ist halt nicht "erlaubt")

    Daher sollte man IMO auch die using std::swap; swap(a, b); Variante verwenden, wenn man selbst "swappenden" Code schreibt.



  • Für "normale" Klassen (also keine Klassentemplates) kann man beides machen: eine freie swap-Funktion im gleichen Namensraum wie die Klasse plus eine Spezialisierung für std::swap. Da es keine partiellen Spezialisierungen für Funktions-Templates gibt, geht das mit Klassentemplates nicht mehr.

    Das Spezialisieren von Funktionstemplates ist etwas, was ich vermeide. Überladen finde ich da sinvoller. Aber das ist ja im std-Namensraum illegal.

    Mit dem, was die Leute sich für C++0x überlegt haben, wird auch eine einzige swap-Funktion im eigenen Namensraum reichen.

    Ist das std:: bei 2) so eigentlich okay und gibt es einen Unterschied zu einem umschliessenden namespace std {} ?

    Ich glaube, das ist nicht erlaubt.

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Mit dem, was die Leute sich für C++0x überlegt haben, wird auch eine einzige swap-Funktion im eigenen Namensraum reichen.

    Das würde es momentan eigentlich auch, wenn sich der Aufrufer etwas achten würde. Oder was meinst du konkret?

    Sebastian Pizer schrieb:

    Ich glaube, das ist nicht erlaubt.

    Das Gefühl habe ich irgendwie auch. Ich habe diese Schreibweise bisher gar noch nie verwendet, aber mal spontan ausprobiert, und es hat normal kompiliert (MSVC++). Weiss jemand Genaueres darüber?



  • Nexus schrieb:

    Irgendwie gerate ich da in Versuchung eines bösen Makro-Hacks... :p

    Nur halb böse 😉 friend-injection machts doch auch, zumindest für die ADL-Swap-Funktion. Siehe boost::operators (wie dies machen hab ich im dritten Operator-Artikel erklärt):

    namespace SomeWhere
    {
    template <class T>
    struct Swappable
    {
      friend void swap(T& lhs, T& rhs)
      { lhs.swap(rhs); }
    };
    
    } //end ns SomeWhere
    
    namespace SomeWhereElse
    {
      class Foo : private SomeWhere::Swappable<Foo>
      {
      public: 
        void swap(Foo& rhs);
      }
    }
    
    int main()
    {
      using SomeWhereElse::Foo;
      Foo a, b;
      swap(a, b);
    }
    

    Leider gehts für std::swap nicht: man darf keine qualifizierten Funktionen als friends definieren, erst recht dürfen keine Template-Spezialisierungen während einer friend-Deklaration definiert werden. Leider kann man auch keine partielle Spezialisierung eines Funktionstemplates machen, schön wäre folgendes:

    template <class T>
    void ::std::swap<SomeWhere::Swappable<T> >(Swappable<T>& lhs, Swappable<T>& rhs)
    {
      (static_cast<T&>(lhs)).swap(static_cast<T&>(rhs));
    }
    

    Das hier wäre eine Überladung, was die verbotene Einführung einer zusätzlichen Funktion in den Namespace std wäre.



  • Nexus schrieb:

    Sebastian Pizer schrieb:

    Ich glaube, das ist nicht erlaubt.

    Das Gefühl habe ich irgendwie auch. Ich habe diese Schreibweise bisher gar noch nie verwendet, aber mal spontan ausprobiert, und es hat normal kompiliert (MSVC++). Weiss jemand Genaueres darüber?

    Um euren Glauben und euer Gefühl zu bestätigen der entsprechende Ausschnitt aus dem Standard:

    ISO/IEC 14882 §17.4.3/2 schrieb:

    An explicit specialization shall be declared in the namespace of which the template is a member [...] Such a declaration may also be a definition. If the declaration is not a definition, the specialization may be defined later in the namespace in which the explicit specialization was declared, or in a namespace that encloses the one in which the explicit specialization was declared.



  • hustbaer, auch dir danke für die Erklärungen, ich hab deinen Post vorhin gar nicht gesehen.

    hustbaer schrieb:

    Daher sollte man IMO auch die using std::swap; swap(a, b); Variante verwenden, wenn man selbst "swappenden" Code schreibt.

    Würde sich dann evtl. sowas empfehlen?

    template <typename T>
    void adl_swap(T& lhs, T& rhs)
    {
        using std::swap;
        swap(lhs, rhs);
    }
    

    pumuckl schrieb:

    Nur halb böse 😉 friend-injection machts doch auch, zumindest für die ADL-Swap-Funktion.

    Stimmt. Ich hab mich nämlich zuerst auch gefragt, wie die Boost.Operators-Entwickler freie Funktionen über Basisklassen bereitstellen wollen... 😉
    Recht praktisch, die Technik.

    pumuckl schrieb:

    Leider gehts für std::swap nicht: man darf keine qualifizierten Funktionen als friends definieren.

    Sorry, hier bin ich nicht ganz nachgekommen, inwiefern meinst du "qualifiziert"? Durch den Namensraum?

    pumuckl schrieb:

    Leider kann man auch keine partielle Spezialisierung eines Funktionstemplates machen, schön wäre folgendes

    Das sollte doch auch mit dem neuen Standard kommen, wenn ich mich nicht täusche...

    pumuckl schrieb:

    Um euren Glauben und euer Gefühl zu bestätigen der entsprechende Ausschnitt aus dem Standard:

    ISO/IEC 14882 §17.4.3/2 schrieb:

    An explicit specialization shall be declared in the namespace of which the template is a member [...] Such a declaration may also be a definition. If the declaration is not a definition, the specialization may be defined later in the namespace in which the explicit specialization was declared, or in a namespace that encloses the one in which the explicit specialization was declared.

    Wenn ich das richtig verstehe, führt das std:: bei void std::swap also gar nicht zur Einbettung in den Namensraum. Das habe ich bisher falsch interpretiert. Wozu wird diese Schreibweise (im Gegensatz zum konventionellen, umschliessenden Namensraum) verwendet?



  • Nexus schrieb:

    pumuckl schrieb:

    Leider gehts für std::swap nicht: man darf keine qualifizierten Funktionen als friends definieren.

    Sorry, hier bin ich nicht ganz nachgekommen, inwiefern meinst du "qualifiziert"? Durch den Namensraum?

    Ist im Grunde das Selbe wie der Auszug aus dem Standard den ich gepostet habe. Du kannst per friend-injection keine Funktion in einen anderen Namespace definieren. Oder andersrum: wenn du einen qualifizierten (also im Grunde alles mit :: drin) als friend deklarierst, kann es eben nur eine Deklaration und keine Definition sein. Definitionen beziehen sich immr auf den umgebenden Namespace, alles andere kann nur dort definiert werden wo es wirklich hingehört.

    pumuckl schrieb:

    Um euren Glauben und euer Gefühl zu bestätigen der entsprechende Ausschnitt aus dem Standard:

    ISO/IEC 14882 §17.4.3/2 schrieb:

    An explicit specialization shall be declared in the namespace of which the template is a member [...] Such a declaration may also be a definition. If the declaration is not a definition, the specialization may be defined later in the namespace in which the explicit specialization was declared, or in a namespace that encloses the one in which the explicit specialization was declared.

    Wenn ich das richtig verstehe, führt das std:: bei void std::swap also gar nicht zur Einbettung in den Namensraum. Das habe ich bisher falsch interpretiert. Wozu wird diese Schreibweise (im Gegensatz zum konventionellen, umschliessenden Namensraum) verwendet?[/quote] Die Schreibweise kann wie der Text besagt dazu verwendet werden, um im globalen namespace eine Spezialisierung von std::swap zu definieren - aber nur wenn sie orher innerhalb von namespace std deklariert wurde. MSVC scheint da etwas freizügiger zu sein, da er die Definition auch in anderen Namespaces und ohne vorherige korrekt plazierte Deklaration schluckt.

    namespace std
    {
      template<> void swap<Foo>(Foo&, Foo&);
      template<> void swap<Bla>(Bla&, Bla&);
    }
    
    template<> void std::swap<Foo>(Foo&, Foo&) {} //OK
    template<> void std::swap<Bar>(Bar&, Bar&) {} //FEHLER - wurde nicht in std deklariert
    
    namespace OuterSpace 
    {
      template<> void ::std::swap<Bla>(Bla&, Bla&) {} //FEHLER - OuterSpace ist kein umgebender namespace von std
    

    }



  • pumuckl schrieb:

    Leider kann man auch keine partielle Spezialisierung eines Funktionstemplates machen, schön wäre folgendes:

    template <class T>
    void swap<SomeWhere::Swappable<T> >(Swappable<T>& lhs, Swappable<T>& rhs)
    {
      (static_cast<T&>(lhs)).swap(static_cast<T&>(rhs));
    }
    

    Das hier wäre eine Überladung, was die verbotene Einführung einer zusätzlichen Funktion in den Namespace std wäre.

    Die <>-Klammern hinter swap deuten auf eine Spezialisierung hin, die in diesem Fall partiell und damit illegal ist. Ein neues Funktionstemplate müsste man ohne diese Klammern schreiben.

    Ich bin kein Freund von Funktions-Template-Spezialisierungen. Die werden zB nicht bei der Auflösung von Überladungen beachtet, auch wenn sie "spezieller" sind. Beispiel:

    #include <iostream>
    
    template<typename U>
    struct s{};
    
    template<typename T>
    void foo(T x) {
      std::cout << "1\n";
    }
    
    template<>
    void foo<s<int> >(s<int> x) {
      std::cout << "2\n";
    }
    
    template<typename U>
    void foo(s<U> x) {
      std::cout << "3\n";
    }
    
    int main() {
      foo(s<int>());
    }
    

    Hier sind 2 Funktions-Templates überladen worden, wobei eines davon für s<int> spezialisiert wurde. Ist die Ausgabe des Programms 1, 2 oder 3? Obwohl eigentlich die Spezialisierung des ersten Templates am "besten passen" sollte, richtet sich die "overload resolution" nicht danach, was es alles so für Spezialisierungen gibt. Die Auflösung wird in diesem Fall durch die "partial ordering"-Regeln bestimmt, die sagen, dass das zweite Funktionstemplate template<typename U> void foo(s<U>) spezieller als das erste Funktionstemplate template<typename T> void foo(T) ist. Dementsprechend ist die Ausgabe des Programms "3".

    Nexus schrieb:

    Sebastian Pizer schrieb:

    Mit dem, was die Leute sich für C++0x überlegt haben, wird auch eine einzige swap-Funktion im eigenen Namensraum reichen.

    Das würde es momentan eigentlich auch, wenn sich der Aufrufer etwas achten würde. Oder was meinst du konkret?

    Stimmt. Man wird auch immer noch nicht davon abgehalten, ADL explizit durch Qualifizierungen auszuschalten. Mit Concepts ist es aber wahrscheinlich schwieriger, in der Hinsicht etwas falsch zu machen:

    #include <concepts>  // std::Swappable
    #include <algorithm> // std::swap
    
    template<typename T> requires std::Swappable<T>
    void rotate_left(T & a, T & b, T & c)
    {
      swap(a,b);
      swap(b,c);
    }
    

    Das wird auch für fundamentale Typen funktionieren, ohne, dass ich "using std::swap;" schreiben muss. Man sollte nur nicht vergessen <algorithm> einzufügen.

    Gruß,
    SP



  • Danke für die Antworten. Concepts werden wahrscheinlich auch sonst einiges zur Schnittstellenvereinheitlichung beitragen. Aber für die nächste Zeit scheint mir pumuckls Vorschlag noch recht flexibel, die automatische Codegenerierung durch Basisklassen ist wirklich nützlich.

    Aber so nebenbei: Wird das wirklich std::Swappable heissen oder war das mehr ein Beispiel? Die Grosschreibung von Bezeichnern im Standard erstaunt mich drum etwas... 😉



  • Nexus schrieb:

    Aber so nebenbei: Wird das wirklich std::Swappable heissen oder war das mehr ein Beispiel? Die Grosschreibung von Bezeichnern im Standard erstaunt mich drum etwas... 😉

    Es sieht ganz so aus. std::Swappable gibt es wirklich im aktuellen Entwurf. Ich hatte es extra nachgeguckt (Kapitel 20.2).


Anmelden zum Antworten