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 templatesusing 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 templatesusing 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ß
WernerNaja, 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