char, short, int, long to hex so richtig?
-
Hallo,
ich habe mir jetzt mal ein template gebastelt welches einen integer type in hex zurück gibt. Ich wollte nun fragen, ob das so in Ordnung ist, oder ob es Verbesserungsvorschläge gibt.
template<typename T1> const std::string int2hex(const T1 value, bool separate = true, char delimiter = ' ') { char size = sizeof(value); std::ostringstream ss; for (char i = size * 2 - 1; i >=0; i--) { ss << std::hex << std::uppercase << (value >> 4 * i & 0xF); if (separate && i > 0 && i % 2 == 0) { ss << delimiter; } } return ss.str(); } int main() { int a = 4857845; std::cout << int2hex(a, true, '-') << std::endl; // 00-4A-1F-F5 short b = 38576; std::cout << int2hex(b, true, ' ') << std::endl; // 96 B0 char c = 128; std::cout << int2hex(c, true, ' ') << std::endl; // 80 long d = 3394894; std::cout << int2hex(d) << std::endl; // 00 33 CD 4E int f = 8903485; std::cout << int2hex(f, false) << std::endl; // 0087DB3D return 0; }
EDIT: Beim einbinden in eine Klasse gibt es leichte Probleme, kann mir jemand sagen warum?
// header class CClass { public: template<typename T1> const tstring int2hex(const T1 value, bool separate = true, char delimiter = ' '); } // cpp template<typename T1> const tstring CClass::int2hex(T1 value, bool separate = true, char delimiter = ' ') { } // CClass.cpp(1339): error C2572: 'CClass::int2hex': Neudefinition des Standardparameters: Parameter 3 CClass.h(217): Siehe Deklaration von 'CClass::int2hex' // CClass.cpp(1339): error C2572: 'CClass::int2hex': Neudefinition des Standardparameters: Parameter 2 CClass.h(217): Siehe Deklaration von 'CClass::int2hex'
mfg Spoocy
-
Die Standardparameter dürfen nur bei der ersten Deklaration angegeben werden.
-
Also jetzt verwirrt mich MS VS, ich hab es in der cpp Datei geändert. Und nun ist es so das der Code mal zu übersetzen geht und mal nicht. Obwohl ich nichts an dem Code geändert habe.
error LNK2001: Nicht aufgelöstes externes Symbol ""public: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const __thiscall CClass::int2hex<unsigned long>(unsigned long,bool,char)"
EDIT: Soweit konnte ich es jetzt schon eingrenzen: In der CClass ist die Function deklariert und definiert. Wenn ich irgendwo in der CClass.cpp den Aufruf
CClassPointer->int2Hex(338478);
dann kann ich in jeder anderen cpp Datei auch int2Hex(unsigned long) machen. Wenn ich aber in der CClass.cpp nun nicht den Aufruf mache, wird keine Funktion mit den default Parametern erstellt. Und das obwohl ich die CClass.h in z.b. AndereClass.cpp einbinde.
-
Templates werden nicht in einer cpp-Datei implementiert, sondern nur im Header. Die komplette Definition des Templates muss an der Verwendungsstelle sichtbar sein.
-
omg, ok das war es. Gibt es sonst irgend welche Verbesserungen an dem Code?
-
Hallo, du brauchst std::hex und std::uppercase nicht jedesmal in der Schleife
setzen, auch wenn der Compiler das evtl. wegoptimiert...template<typename T1> const std::string int2hex(const T1 value, bool separate = true, char delimiter = ' ') { char size = sizeof(value); std::ostringstream ss; // Setze hex und uppercase einmalig hier, das reicht ss << std::hex << std::uppercase; // oder std::setioflags() for (char i = size * 2 - 1; i >=0; i--) { ss << (value >> 4 * i & 0xF); if (separate && i > 0 && i % 2 == 0) { ss << delimiter; } } return ss.str(); }
Dann würde ich mir den 'seperate' sparen und das 'delimiter'
in der if-Abfrage prüfen....template<typename T1> const std::string int2hex(const T1 value, char delimiter = ' ') { ... // char('\0') == int(0) == bool(false) if (delimiter && i > 0 && i % 2 == 0) { ss << delimiter; } ... } int main() { // nicht getestet.... int f = 8903485; std::cout << int2hex(f, '\0') << '\n'; // 0087DB3D std::cout << int2hex(f, 0) << '\n'; // int(0) == char('\0') 0087DB3D std::cout << int2hex(f, false) << '\n'; // bool(false) == char('\0') 0087DB3D std::cout << int2hex(f) << '\n'; // 00 87 DB 3D std::cout << int2hex(f, '-') << '\n'; // 00-87-DB-3D }
Wenn die Hex-Zahlen meistens ohne Trennsymbol ausgegeben werden sollen
würde ich es umgekehrt machen:template<typename T1> const std::string int2hex(const T1 value, char delimiter = '\0') { ... } int main() { // nicht getestet.... int f = 8903485; std::cout << int2hex(f) << '\n'; // 0087DB3D std::cout << int2hex(f, ' ') << '\n'; // 00 87 DB 3D std::cout << int2hex(f, '-') << '\n'; // 00-87-DB-3D }
Einen schönen Tag noch...
dirkski
-
Man könnte noch:
if (delimiter && i > 0 && i % 2 == 0)
if (delimiter && i && i % 2 == 0)
machen oder? Denn wenn i gleich 0 ist, bricht die abfrage ja schon ab. Aber ich meine gelesen zu haben, das dieses verhalten (was ich so aus perl und php kennt) in c++ nicht zwangsweise so sein muss.
Wobei ich nicht weis ob es überhaupt einen unterschied macht
-
MrSpoocy schrieb:
Man könnte noch:
if (delimiter && i > 0 && i % 2 == 0)
if (delimiter && i && i % 2 == 0)
machen oder? Denn wenn i gleich 0 ist, bricht die abfrage ja schon ab. Aber ich meine gelesen zu haben, das dieses verhalten (was ich so aus perl und php kennt) in c++ nicht zwangsweise so sein muss.
Wobei ich nicht weis ob es überhaupt einen unterschied macht
Ja, müßte gehen da i ja niemals kleiner als 0 werden kann.
0 == false, alles andere == true. Hab mir das aber nicht so genau angeguckt,
tut ja was es sollEDIT: Vielleicht weiß jemand von den >= C++11-Spezialisten ob sich für
die Rückgabe ein std::move() lohnt. Ich blicke da noch nicht so durch.#include <utility> template<typename T1> const std::string int2hex(const T1 value, char delimiter = '\0') { ... return (std::move(ss.str())); }
hth
dirkski
-
template<typename T1> const std::string //const weg, weil das Move verhindert int2hex(const T1 value, bool separate = true, char delimiter = ' ') { char size = sizeof(value); //der Typ ist size_t std::ostringstream ss; for (char i = size * 2 - 1; i >=0; i--) //char kann unsigned sein. Klammern wären schon { ss << std::hex << std::uppercase << (value >> 4 * i & 0xF); //Klammern fehlen if (separate && i > 0 && i % 2 == 0) //Klammern fehlen mir persönlich auch hier { ss << delimiter; } } return ss.str(); } }
-
TyRoXx schrieb:
... for (char i = size * 2 - 1; i >=0; i--) //char kann unsigned sein ...
Jep, das ist übel, ist mir gar nicht aufgefallen
Wenn char unsigned ist würde nach dem (eigentlich) letzten
Schleifendurchlauf (i == 0) i nochmal um 1 decrementiert werden. Ergebnis
wäre ein Überlauf und i hätte wieder den Wert 255. Das ist dann eine EndlosschleifeAls Schleifenvariablen besser nie char verwenden sondern explizit signed.
Oder einfach int.Einen schönen Tag noch...
dirkski
-
Noch ein Vorschlag: Wenn eine Template-Funktion nur mit einer bestimmten Klasse von Typen umgehen kann, schränke ich diese gerne explizit ein.
Das kann man mit einemstd::enable_if
machen, oder in diesem Fall auch gut mit einemstatic_assert
(simpler und liefert eine lesbarere Fehlermeldung):const std::string int2hex(const T1 value, bool separate = true, char delimiter = ' ') { static_assert(std::is_integral<T1>::value, "int2hex supports integral types only"); ... }
manni66 schrieb:
Templates werden nicht in einer cpp-Datei implementiert, sondern nur im Header. Die komplette Definition des Templates muss an der Verwendungsstelle sichtbar sein.
Den ersten Satz würde ich streichen, der zweite bringt es eher auf den Punkt.
("Private" Templates, die man nur in einer .cpp-Datei benötigt sind nicht unbedingt "falsch").dirkski schrieb:
EDIT: Vielleicht weiß jemand von den >= C++11-Spezialisten ob sich für
die Rückgabe ein std::move() lohnt. Ich blicke da noch nicht so durch.#include <utility> template<typename T1> const std::string int2hex(const T1 value, char delimiter = '\0') { ... return (std::move(ss.str())); }
Da der Rückgabewert von
ss.str()
ohnehin ein rvalue ist, sollte hier so oder so ein Move stattfinden. Ich halte dasstd::move
daher für überflüssig.Möglicherweise könnte jedoch folgender Code effizienter sein:
template<typename T1> std::string int2hex(const T1 value, char delimiter = '\0') { ... auto result = ss.str(); return result; }
Grund: Handelt es sich bei dem Ausdruck der im Return-Statement zurückgegeben wird um den Namen eines automatischen Objekts (lokale Stack-Variable),
dann erlaubt es der Standard dem Compiler das Objekt direkt im Rückgabewert zu konstruieren (Return Value Optimization).
Ebenso darf der Compiler das implizite Move wegoptimieren (z.B.result = ss.str()
undhex = int2hex(value)
) und das Objekt auch hier direkt in der Zielvariablen konstruieren.Ich habe das nicht getestet und man müsste mal tatsächlich prüfen, was für einen Code der Compiler erzeugt, aber meines erachtens erlaubt der Standard dem Compiler bei obigem Code
z.B.std::string hex = int2hex(value);
so zu optimieren, dass ein interner String, der innerhalb vonostringstream::str()
eine lokale Variable ist, direkt inhex
konstruiert wird.
Natürlich ist das abhängig davon, wie die Funktionostringstream::str()
implementiert ist und ob deren Code überhaupt optimiert werden kann (also sichtbar für den Compiler und nicht von extern eingebunden).
Meiner Meinung nach sollte das allerdings nicht schlechter als einreturn ss.str()
bzw. returnstd::move(...)
sein (ich erwarte dass bei den zweiresult
-Zeilen da oben zumindest ein Move wegoptimiert wird).Gruss,
Finnegan
-
Finnegan schrieb:
Das kann man mit einem
std::enable_if
machen, oder in diesem Fall auch gut mit einemstatic_assert
(simpler und liefert eine lesbarere Fehlermeldung):enable_if
ist dafür zuständig, ein Funktionstemplate nur in bestimmten Situationen verfügbar zu machen.static_assert
wird (i.d.R.) verwendet, um die Validität und Sinnhaftigkeit eines bereits ausgewählten (Funktions)templates bei der Instantiierung zu prüfen. Hier iststatic_assert
besser, da der Name des Funktionstemplates selbst schon Ganzzahlen impliziert, aber die beiden sind meistens nicht sinnvoll austauschbar, weil sie eben unterschiedliche Dinge tun (sollten).
-
Grund: Handelt es sich bei dem Ausdruck der im Return-Statement zurückgegeben wird um den Namen eines automatischen Objekts (lokale Stack-Variable),
dann erlaubt es der Standard dem Compiler das Objekt direkt im Rückgabewert zu konstruieren (Return Value Optimization).Nein, deins ist genauso effizient wie die vorige Variante. Die Temporary, die der Rückgabewert von
str
ist, wird direkt im Endobjekt erzeugt (s.u.). Falls die Implementierung innerhalbstr
es erlaubt (sehr wahrscheinlich) werden gar alle Moves elidiert.(also sichtbar für den Compiler und nicht von extern eingebunden).
Das ist zwangsläufig, da
str
ein Temploid ist. (Edit: Außer die Implementierung ist unglaublich drollig und packt eine explizite Instantiierung rein und die Implementierung woanders, also nur fürostringstream<char>
.)Wobei
const
im Rückgabewert tatsächlich etwas völlig sinnfreies ist. Entweder es verhindert potenziell Move-Semantik für Klassentypen, oder hat gar keinen Effekt, daconst
von dem Typ eines andersartigen prvalue Rückgabewerts einfach abgeschnippelt wird.copy elision wird jedoch trotzdem angewandt. Schließlich können wir noch kopieren. Zuerst wird der Rückgabewert direkt konstruiert, und dieser wird wiederum direkt im entsprechenden Endobjekt konstruiert - beide dieser Schritte werden durch den folgenden Paragraphen ermöglicht:
12.8/(31.3) schrieb:
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same type (ignoring cv-qualification), the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
Der einzige Nachteil besteht in dem Falle wo copy elision aus unerfindlichen Gründen (e.g.
-fno-elide-constructors
) nicht angewandt wird.Zwar wird laut 12.8/32 ein implizites Move generiert, i.e. der Code ohne [c]move [/c]ist tatsächlich äquivalent zu [..]
Mein Fehler, selbst dass ist dann nicht möglich. Es werden daher zwei Kopien(!) gemacht.
-
Arcoth schrieb:
Finnegan schrieb:
Das kann man mit einem
std::enable_if
machen, oder in diesem Fall auch gut mit einemstatic_assert
(simpler und liefert eine lesbarere Fehlermeldung):enable_if
ist dafür zuständig, ein Funktionstemplate nur in bestimmten Situationen verfügbar zu machen.static_assert
wird (i.d.R.) verwendet, um die Validität und Sinnhaftigkeit eines bereits ausgewählten (Funktions)templates bei der Instantiierung zu prüfen. Hier iststatic_assert
besser, da der Name des Funktionstemplates selbst schon Ganzzahlen impliziert, aber die beiden sind meistens nicht sinnvoll austauschbar, weil sie eben unterschiedliche Dinge tun (sollten).Ja, eine Möglichkeit das einzuschränken ist eben die Funktion nur für Integer-Typen zu definieren, das ist die
enable_if
-Variante.
Diese ist unterm Strich dasselbe wie eine Reihe von Nicht-Template-Funktionen nur für Integer-Typen schreiben.
Das kann durchaus auch eine valide Lösung sein um zu verhindern, dass das Template mit einem Typen instanziert wird,
für den es nicht geschrieben wurde (static_assert
find ich ja auch besser).Dass
enable_if
undstatic_assert
immer frei austauschbar wären, habe ich auch nicht behauptet.Finnegan
-
Arcoth schrieb:
Grund: Handelt es sich bei dem Ausdruck der im Return-Statement zurückgegeben wird um den Namen eines automatischen Objekts (lokale Stack-Variable),
dann erlaubt es der Standard dem Compiler das Objekt direkt im Rückgabewert zu konstruieren (Return Value Optimization).Nein, deins ist genauso effizient wie die vorige Variante. Die Temporary, die der Rückgabewert von
str
ist, wird direkt im Endobjekt erzeugt (s.u.). Falls die Implementierung innerhalbstr
es erlaubt (sehr wahrscheinlich) werden gar alle Moves elidiert.Jetzt wo ichs mir nochmal ansehe, denke ich dass du damit recht hast, und ich bin auch froh darüber, da es keine Verrenkungen erfordert.
Ich habe mich bei meinem Gedankengang zu sehr auf RVO fixiert: Der Ausdruckss.str()
ist eben nicht der Name einer lokalen Variable und kann daher nicht für RVO herangezogen werden.
Gleichzeitig gilt aber noch der von dir zitierte Absatz zur Copy Elision aus dem Standard:
So wie ich das verstehe wird dann bei einem simplenreturn ss.str()
innerhalb vonint2hex()
zwar direkt keine RVO gemacht (sondern lediglich innerhalb von ostringstream::str() bzw.
in einer eventuellen weiteren Hilfsfunktion), aufgrund von Copy Elision können dann allerdings die Rückgabewerte "durchgereicht" werden so dass der String dennoch direkt inhex
konstruiert wird.
Macht das Sinn?Gruss,
Finnegan
-
Also das ganz wird für mich zu hoch, dafür kann ich c++ einfach nicht gut genug. Ich bin jetzt auch unschlüssig was am ende die "richtige" Lösung ist.
Letzter Stand ist:
template<typename T1> tstring int2hex(const T1 value, char delimiter = ' ') { signed char size = sizeof(value); tostringstream ss; ss << std::hex << std::uppercase; for (signed char i = size * 2 - 1; i >= 0; i--) { ss << ((value >> (4 * i)) & 0xF); if (delimiter && i && (i % 2) == 0) { ss << delimiter; } } return ss.str(); }
-
Ich finde die Schleife sehr verwirrend. Ohne ein paar Unit Tests würde ich nicht meine Hand dafür ins Feuer legen, dass die das richtige tut.
-
TyRoXx schrieb:
Ich finde die Schleife sehr verwirrend. Ohne ein paar Unit Tests würde ich nicht meine Hand dafür ins Feuer legen, dass die das richtige tut.
Naja, minimum und maximum bei unsigned- sowie signed-typen funzt das...
Und ein paar Zahlen dazwischen auch. Test bestanden
Einen schönen Abend noch...
dirkskiEdit: Aber was ist ein tstring?
-
tstring und tostringstream sind einfach typedef für std::(w)string etc.
-
MrSpoocy schrieb:
Ich bin jetzt auch unschlüssig was am ende die "richtige" Lösung ist.
Die "richtige" Lösung ist natürlich nur ein kurzlebiger Proxy, der vom op<<(ostream verstanden wird.
Und lass die Kindereien in Sachen ">>4" und "i&0xF", wir sind nicht mehr im Gipskrieg.