swap, abs & Co im template mit oder ohne std::



  • Mal angenommen man erstellt ein Template, in dem ein - sagen wir mal - allerwelts-Funktions-Template wie etwa swap oder abs aufgerufen wird. Beispiel:

    template< typename T >
    void pair_sort( T& first, T& second )
    {
        if( second < first )
        {
            using namespace std;    // damit swap( int, int ) auch funkt
            swap( first, second );  // <-- std::swap wäre m.E. falsch
        }
    }
    

    swap und abs sind schon für etliche Typen (bzw. Templates) im namespace std definiert. Gleichzeitig steht es jedem frei für eigene Typen swap oder abs zu überladen. Prominentes Beispiel ist abs(boost::rational<T>).
    Als Schreiber des Templates kann ich nicht sagen ob T ein Container aus der STL eine selbstgestrickte Klasse mit implementierter swap-Funktion oder ein int ist.
    Wie rufe ich also das swap im Template auf?
    - mit std::swap, so liefert mir das Template für die selbstgestrickte Klasse den falschen (wahrscheinlich suboptimalen) Code, da das std::swap verwendet wird, was dann den Kopy-Konstruktor und den Zuweisungs-Operator meiner Klasse aufruft.
    - ohne std:: davor, so übersetzt es nicht, wenn T ein int ist, da das swap( int, int ) nur im namespace std existiert.

    Deshalb bin ich der Meinung, dass man am besten das using namespace std davor setzt, so dass sich der Compiler selbst die korrekte Funktion aussuchen kann.

    Wirft man jetzt aber einen Blick in die Implementierung der (Dinkumware-)STL so findet man dort das explicite std::swap vor, was zwar i.A. funktionieren wird, aber eben bei selbst geschriebener swap-Funktion, die sich nicht im namespace std befindet (darf sie das?, soll sie das?) nicht das gewünschte Ergebnis liefert.

    Wie ist Eure Meinung dazu?

    Gruß
    Werner



  • Hi,

    also ich war bis vor ein paar Monaten großer "Vollqualifizierer" ("std:: as std:: can") .... bis mir irgendwann mal genau DAS auffiel: Man nimmt sich Möglichkeiten - schafft sich Abhängigkeiten.

    Inzwischen qualifiziere ich nicht und importiere auch nicht via using, sondern überlasse es dem Nutzer des templates. Nur der kann entscheiden, ob der Typ, mit dem er mein template instantiiert, mit std- oder anderen Funktionen am besten klarkommt. Vielleicht WILL er ja auch sein spezialisiertes swap einbinden, wo das std::swap zwar funktionierte, aber nicht so performant, ohne den gewünschten Seiteneffekt, ...
    Ich sehe das eher als Flexibilisierung zugunsten des Aufrufers.

    Erstmal wirkt es natürlich seltsam, wenn der Nutzer meines templates using std::swap; deklarieren muß, obwohl er selbst kein swap verwendet, aber IMO gehört die Verwendung dieser Funktion mit zur Schnittstelle meines templates (und es ist IMO eine Schwäche von C++, dass ich das dem Nutzer auf keinem anderen Weg standardisiert mitteilen kann) ....

    Man muß einfach wissen, welche Anforderungen ein template an den "instantiierenden Typen" stellt ... ist eigentlich nichts Neues, das kennt man z.B. von der "Kopierbarkeit" bei std::vector....

    Gruß,

    Simon2.



  • Simon2 schrieb:

    .. überlasse es dem Nutzer des templates. [skip]
    Erstmal wirkt es natürlich seltsam, wenn der Nutzer meines templates using std::swap; deklarieren muß, obwohl er selbst kein swap verwendet ..

    Hallo Simon.

    Das ist eine Möglichkeit, die ich noch nicht bedacht habe. Aber es ist wirklich seltsam, und ob ein Anwender bei dem das T ein int ist damit glücklich wird, möchte ich bezweifeln.

    Gruß
    Werner



  • Wirft man jetzt aber einen Blick in die Implementierung der (Dinkumware-)STL so findet man dort das explicite std::swap vor, was zwar i.A. funktionieren wird, aber eben bei selbst geschriebener swap-Funktion, die sich nicht im namespace std befindet (darf sie das?, soll sie das?) nicht das gewünschte Ergebnis liefert.

    Man darf std::swap im Namespace std spezialisieren. Man darf aber leider kein neues swap zu std hinzufügen. D.h. für normale Typen, also nicht Templates, sollte man wie folgt vorgehen:
    a) eigenes swap im Namespace des Typen definieren
    b) std::swap im Namespace std für den Typen spezialisieren.

    So funktioniert beides: ein unqualifizierter Aufruf (-> Argumennt dependent lookup) sowie ein mit std-qualifizierter Aufruf (-> Auswahl der Spezialisierung).

    Für Templates fällt b) leider weg. Hier kann man lediglich std::swap für einzelne Instanzen des Templates spezialisieren.

    Deine Idee mit dem using namespace std halt ich für den richtigen Ansatz. Allerdings würde ich keine using-direktive sondern lediglich eine using-Deklaration verwenden. Sprich ein einfaches using std::swap vor den Aufruf.



  • Werner Salomon schrieb:

    Simon2 schrieb:

    .. überlasse es dem Nutzer des templates. [skip]
    Erstmal wirkt es natürlich seltsam, wenn der Nutzer meines templates using std::swap; deklarieren muß, obwohl er selbst kein swap verwendet ..

    Hallo Simon.

    Das ist eine Möglichkeit, die ich noch nicht bedacht habe. Aber es ist wirklich seltsam, und ob ein Anwender bei dem das T ein int ist damit glücklich wird, möchte ich bezweifeln.

    Gruß
    Werner

    Naja, so wie sie sich dran gewöhnt haben, geeignete copy-Ctoren, operator=(), compare-Funktoren, ... zur Verfügung zu stellen, werden sie sich auch daran gewöhnen.
    Letztlich nutzen templates nunmal die Eigenschaften der übergebenen Typen und dazu müssen diese Eigenschaften bereitgestellt werden.
    UND: Für Viele Sachen (cout & Co) wird der Anwender das nicht merken, weil er die sowieso schon importiert hat.

    Alternativ kannst Du natürlich einen zusätzlichen template-Parameter einführen und Dir einen swap-Funktor übergeben lassen (im Extremfall ein ganzes traits-Konstrukt) - das ist dann wenigstens im Code dokumentiert (und Du kannst einen Default annehmen).

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Alternativ kannst Du natürlich einen zusätzlichen template-Parameter einführen und Dir einen swap-Funktor übergeben lassen (im Extremfall ein ganzes traits-Konstrukt)

    Die Verwendung einer Traits-Klasse ist zumindest für weniger allgemeine Typen/Algorithmen eine gute Idee. Allerdings würde ich die nicht als Parameter übergeben. Vielmehr würde ich die Traits-Klasse im Namespace der Template-Library definieren, mindestens eine allgemeine Implementation bereitstellen und dann den Nutzer der Lib auffordern, selbige für etwaige Sonderfälle zu spezialisieren.



  • Eine Traits-Klasse einzuführen, die dann innerhalb des Templates gerufen wird, kam mir auch schon in den Sinn. Also etwa sowas

    // Code im template ...
        spezial_swap( x1, x2, swap_trait< T >::istEingebauterTyp() );
    
    // -- mit
    struct Ja {};
    struct Nein {};
    template< typename T > struct swap_traits 
    {
        typedef Nein istEingebauterTyp;
    };
    template<> struct< int > swap_traits // und für char, short, double usw.
    {
        typedef Ja istEingebauterTyp;
    };
    
    // -- und
    template< typename T >
    void spezial_swap( T& a, T& b, Ja )
    {
        std::swap( a, b );
    }
    

    aber zurück zur ursprünglichen Frage. Wo ist bei dieser Konstruktion der Vorteil gegenüber dem einfachen

    {
            using namespace std;    // damit swap( int, int ) auch funkt
            swap( first, second );
        }
    

    meinetwegen auch mit using std::swap .

    Gruß
    Werner



  • @Werner Salomon
    Was die Traitsklasse angeht, so würde ich die einfachste Variante vorschlagen:

    namespace YourLib {
    template <class T>
    struct Swap {
        static void swap(T& t, T& u) { std::swap(t, u); }
    };
    
    template <class T>
    void func(T& o, T& k) {
       YourLib::Swap<T>::swap(o, k);
       ...
    }
    }
    

    Client-Code kann dann einfach YourLib::Swap spezialisieren.



  • HumeSikkins schrieb:

    Man darf std::swap im Namespace std spezialisieren. Man darf aber leider kein neues swap zu std hinzufügen. D.h. für normale Typen, also nicht Templates, sollte man wie folgt vorgehen:
    a) eigenes swap im Namespace des Typen definieren
    b) std::swap im Namespace std für den Typen spezialisieren.

    Hallo Hume,

    Oops, ich hatte diese Deine Antwort zu spät gesehen. Ich stehe jetzt etwas auf dem Schlauch. Was heißt 'darf spezialisieren, aber nicht hinzufügen'? Beispiel

    class MDK  // MeineDickeKlasse
    {
       // viel Zeug
       friend void swap( MDK& a, MDK& b );
    };
    

    Was ist jetzt

    namespace std
    {
       swap( MDK& a, MDK& b )
       {
           ::swap( a, b );
       }
    }
    

    ist das spezialisieren oder hinzufügen? Und wie geht dann das jeweils andere?

    Gruß
    Werner



  • HumeSikkins schrieb:

    Client-Code kann dann einfach YourLib::Swap spezialisieren.

    Hallo Hume,

    Was der Client aber vielleicht doof findet, da er nicht nur 'YourLib' sondern auch 'MyLib' und 'Lib3' verwendet, wo eine ähnliche Problematik auftaucht. Ein Client wird doch (wenn überhaupt) ein

    swap( ClientKlasse& a, ClientKlasse& b )
    

    zur Verfügung stellen, was in jedem Fall über namespace lookup gefunden werden sollte.

    Gruß
    Werner



  • Hallo,
    std::swap ist eine Templatefunktion, d.h. irgendwo in namspace std steht:

    namspace std {
    template <class T>
    void swap(T& t, T& u);
    }
    

    Einige Standard-Header definieren auch noch Überladungen für swap. Z.B. <vector>

    namspace std {
    // Überladung - nicht Spezialisierung!
    template <class T>
    void swap(std::vector<T>& t, std::vector<T>& u);
    }
    

    Du als "normaler" C++ Benutzer, darfst std::swap in std spezialisieren, aber nicht überladen. D.h.

    class MDK  // MeineDickeKlasse
    {
       // viel Zeug
       friend void swap( MDK& a, MDK& b );
    };
    
    namespace std {
    // OK - Spezialisierung
    template <>
    void swap(MDK& lhs, MDK& rhs);
    }
    

    aber nicht:

    template <class T>
    class MDK  // MeineDickeKlasse
    {
       // viel Zeug
       friend void swap( MDK& a, MDK& b );
    };
    
    namespace std {
    // NICHT ERLAUBT - Überladung!
    template <class T>
    void swap(MDK<T>& lhs, MDK<T>& rhs);
    }
    


  • Werner Salomon schrieb:

    Was der Client aber vielleicht doof findet, da er nicht nur 'YourLib' sondern auch 'MyLib' und 'Lib3' verwendet, wo eine ähnliche Problematik auftaucht.

    Exakt. Deshalb habe ich die Traitsklasse ja auch nur für "weniger allgemeine Typen/Algorithmen" vorgeschlagen. Für sowas wie swap landest du sonst direkt bei der "tausend-kleine-Adapter"-Problematik.

    Leztlich liegt der Fehler beim Standard. Hätte man dort einfach eine Klasse für swap verwendet, gäbe es kein Problem, da man diese dann beliebig spezialisieren könnte und Template-Code so immer std::swap schreiben könnte.



  • HumeSikkins schrieb:

    Du als "normaler" C++ Benutzer, darfst std::swap in std spezialisieren, aber nicht überladen.

    Danke für Deine ausführlichen Erläuterung. Also der Unterschied ist nur template oder nicht template, oder! Kannst Du mir jetzt noch sagen, wo das im Standard steht.

    Gruß
    Werner



  • Werner Salomon schrieb:

    Kannst Du mir jetzt noch sagen, wo das im Standard steht.

    17.4.3.1/1:

    It is undefined for a C++ program to add declarations or definitions to namespace std or namespaces within namespace std unless otherwise specified. A program may add template specializations for any standard library template to namespace std. Such a specialization (complete or partial) of a standard library template results in undefined behavior unless the declaration depends on a user-defined name of external linkage and unless the specialization meets the standard library requirements for the original template.

    Da std::swap ein Funktionstemplate ist und da Funktionstemplate nur vollständig, nicht aber partiell spezialisiert werden können, ergibt sich für swap das von mir gesagte.



  • @Hume: Danke 👍


Anmelden zum Antworten