Gibt es eine sprintf ähnliche Funktion für std::string ???



  • Eisflamme schrieb:

    In C++ baut man vieles durch solche Streams auf. Das mag Dir unorthodox erscheinen, wenn Du damit nicht vertraut bist, tatsächlich hat es aber viele Vorteile.

    Und ebenso viele Nachteile. Weiß ich selbst. Wenn dich die Frage tatsächlich interessiert, gibts in meiner Signatur einen Link zu einem Artikel.

    Eisflamme schrieb:

    Und ja, es ist mein Ernst. Aber ich muss zugeben, dass ich gar nicht verstehe, was du mit Lokalisation des Strings meinst.

    Dann sag das doch gleich 😉
    Ich hätte gedacht, daß gettext ein bekanntes oder zumindest ein googletaugliches Stichwort ist. Aber bitte: mit Lokalisierung alias Internationalisierung meine ich das Bereitstellen von Software in mehreren Anwendersprachen. Gibt man einen Format-String wie den obigen aus, so wird man ihn in mehreren Sprachen bereitstellen wollen. Und spätestens da ist die Stream-Lösung völlig unpraktikabel; ich verweise für ein Beispiel auf den erwähnten Artikel.



  • 314159265358979 schrieb:

    void print(std::ostream& s, const char* str)
    {
            s << str;
    }
     
    template <typename Head, typename... Tail>
    void print(std::ostream& s, const char* str, Head&& head, Tail&&... tail)
    {
            while(*str != '%')
                    s << *str++;
            s << head;
     
            print(s, ++str, std::forward<Tail>(tail)...);
    }
     
    template <typename... Args>
    std::string print(const std::string& format, Args&&... args)
    {
            std::ostringstream ss;
            print(ss, format.c_str(), std::forward<Args>(args)...);
            return ss.str();
    }
    

    Sicherlich nicht das schnellste, aber typsicher.

    Schönes Beispiel für Perfect Forwarding 👍
    Auch wenn es natürlich noch keine explizite Formatierung unterstützt ala
    printf, aber die kann man ja ergänzen...



  • Wow, danke für die vielen Tips! 🙂

    was spricht gegen

    std::ostringstream ostr;
    ostr << "Koordinaten des Punktes: (" << << setfill('0') << setw(3) << x << ' ' << y << ')';
    std:.string S = ostr.str();
    

    Nun ja,

    Übersichtlichkeit ist hier so eine Sache. Die sprintf Anweisung ist zwar kompakt aber er ist nicht für jeden lesbar welcher keine Ahnung von Formatstrings hat. Einen etwas unverständlicheren Formatstring habe ich mal bei einem sscanf Befehl benutzt:

    %*[^:]: V = %lf
    

    Da ist die Sache mit den Stringstreams besser lesbar aber halt auch länger.

    Aber hier sehe ich noch eine kleine Schwierigkeit denn der sprintf Befehl mit seinen Formatstrings ist überaus mächtig. Da müsste ich mich halt mal in Stringstreams einarbeiten. 🙂



  • Achsooo, okay, ich hatte Lokalisation gar nicht in Verbindung mit Streams gebracht und gettext erschien mir ohne das zu prüfen schon nicht googlebar, aber jetzt habe ich das gelesen.

    Konkret: Lokalisation geht nicht auf Grund von anderen Satzstellungen in anderen Sprachen, richtig? Sonst wären Streams ja genau so gut.



  • Bitte ein Bit schrieb:

    Da müsste ich mich halt mal in Stringstreams einarbeiten. 🙂

    Wenn du das noch nicht getan hast solltest du das auf jeden Fall mal machen - unabhängig davon ob es für deinen Fall jetzt ausreichend ist oder nicht.

    audacia schrieb:

    Es ist unübersichtlich, doppelt so lang und macht es unmöglich, den String mit gettext() o.ä. zu lokalisieren?

    - Unübersichtlichkeit ist realtiv, wie auch schon andere bemerkt haben. Ob man jetzt die Streamoperatoren und -manipulatoren übersichtlicher findet als einen Formatstring voller Platzhalter, wo die ganzen Ersetzungen erst am Schluss kommen ist wohl Gewöhnungs- und Geschmackssache.
    - Doppelt so lang ist fast kein Argument. Wenn mein Code kurz und bündig sein soll nehm ich Perl und veranstalte Regex-Schlachten oder was anderes in der Art. Länge beeinflusst nur die Tipparbeit und die Übersichtlichkeit. Letztere s.o. Code wird einmal geschrieben und N-mal gelesen -> Lesbarkeit und Ausdrucksstärke schlägt Tippfaulheit bei weitem.
    - gettext/lokalisierung war nirgends gefordert, sind hier also kein Grund, an den für C++ eher unüblichen C-Formatstrings festzuhalten. Wenns gefordert ist fällt mein Beispiel natürlich weg und irgendeine typsichere Format-Bibliothek ist angebracht.



  • Eisflamme schrieb:

    Konkret: Lokalisation geht nicht auf Grund von anderen Satzstellungen in anderen Sprachen, richtig?

    Nö. Das ist nur das KO-Argument. (Außerdem kann man da mit printf() auch ins Schwimmen geraten, weil es zumindest nach dem Standard keine positionsbezogene Referenzierung ermöglicht; da muß man dann entweder eine Erweiterung oder gleich eine 3rd-party-Lösung wie String::Format() oder boost.format verwenden.) Wenn du gettext verwendest, bekommt der Übersetzer einfach eine Liste der Strings, die er in die jeweilige Sprache anpassen muß. Mit Format-Strings etwa so (ich bediene mich einfach mal in der Delphi-Laufzeitbibliothek):

    'A class named %s already exists'
    'Cannot assign a %s to a %s'
    'CheckSynchronize called from thread $%x, which is NOT the main thread'
    'Thread Error: %s (%d)'
    ...

    Wenn du die Strings zerlegst und die Einzelteile mit gettext internationalisieren willst, bekommt der Übersetzer das:

    'A class named '
    ' already exists'
    'Cannot assign a '
    ' to a '
    'CheckSynchronize called from thread '
    ', which is NOT the main thread'
    'Thread Error: '
    ' ('
    ')'
    ...

    Und soweit ich weiß, haushaltet gettext mit der Reihenfolge nach eigenem Gutdünken (z.B., um identische Strings zusammenzulegen), so daß es sehr unwahrscheinlich ist, daß obige Schnipsel in einer sinngebenden Reihenfolge auftauchen.

    Kurzum: gettext und Streams geht einfach nicht.

    pumuckl schrieb:

    - gettext/lokalisierung war nirgends gefordert, sind hier also kein Grund, an den für C++ eher unüblichen C-Formatstrings festzuhalten. Wenns gefordert ist fällt mein Beispiel natürlich weg und irgendeine typsichere Format-Bibliothek ist angebracht.

    Und was ist mit dem bedauernswerten Menschen, der in zwei Jahren den Auftrag bekommt, deinen Quelltext internationalisierungsfähig zu machen? Dem wird nichts übrigbleiben, als all dein wunderschön übersichtliches, maximal ausführliches, ideal lesbares Streamgeschubse leider wegwerfen zu müssen.

    Formatierung durch Streams sollte man grundsätzlich nicht benutzen, wenn lokalisierungsspezifische Dinge (z.B. Strings in einer bestimmten Sprache) im Spiel sind. Das ist ähnlich töricht, wie wenn du einfach den Speicher nicht freigibst, nur weil dein Programm in seiner gegenwärtigen Inkarnation z.B. nur auf der Kommandozeile aufgerufen wird und dort maximal 1-2s läuft, so daß das System ohnehin aufräumt. Wehe dem, der das hinterher aufräumen muß.



  • audacia schrieb:

    ...

    +1



  • audacia schrieb:

    pumuckl schrieb:

    - gettext/lokalisierung war nirgends gefordert, sind hier also kein Grund, an den für C++ eher unüblichen C-Formatstrings festzuhalten. Wenns gefordert ist fällt mein Beispiel natürlich weg und irgendeine typsichere Format-Bibliothek ist angebracht.

    Und was ist mit dem bedauernswerten Menschen, der in zwei Jahren den Auftrag bekommt, deinen Quelltext internationalisierungsfähig zu machen? [...]

    Nochmal: Lokalisierung (jetzt oder die Möglichkeit für die Zukunft) war hier nicht gefordert. Wenn es sich um kurzlebige Spielereien handelt , wie >80% der Problemstellungen hier im Forum, ist Streamgeschubse durchaus ok. Der Mensch, der in 2 Jahren meinen Quellcode internationalisieren muss, wird auch kein Stremgeschubse vorfinden. Wenn ich aber weiß, dass mein Code Wegwerfcode ist wie meistens hier im Forum, bei Spielereien, kleinen Lernprogrammen und POCs zu Hause, dann weiß ich, dass es nie zu einer Internationalisierung kommen wird und werde mich auf Bordmittel beschränken, statt zusätzliche Bibliotheken einzubinden oder Zeit für eine schöne, erweiterbare Lösung aufzuwenden, die eh nie erweitert wird.
    Wie bereits betont muss man sich natürlich bewusst sein, wie erweiterbar und ausreichend eine Lösung wie das Streamschubsen ist. Man muss sich aber andersrum auch bewusst sein, wann Generalisierung, Erweiterbarkeit etc. keinen Sinn mehr machen.
    Deshalb nochmal: Da wo Lokalisierung auf jeden Fall jetzt und in Zukunft ausfällt, braucht man keine lokalisierbaren, mit gettext verwurstbaren Strings.
    Ob hier so ein Fall vorliegt muss natürlich der OP selber entscheiden. Aber schön dass wir das Thema angesprochen haben 😉



  • <a href= schrieb:

    audacias Homepage">Interessanterweise ist es in durchaus möglich, die oben aufgezählten Probleme systematisch zu vermeiden. Zunächst kann die Speicherverwaltung automatisiert werden, um die Gefahr von Pufferüberläufen bei sprintf() zu beseitigen:

    #include <string>
    #include <cstdarg>
    #pragma hdrstop
    
    std::string str_printf (const char* format, ...)
    {
        std::va_list args;
        std::string retval;
    
        va_start (args, format);
    
            // Bei der Übergabe von 0 als Puffer ermittelt die Funktion
            // nur die Länge des entstehenden Strings.
        retval.resize (std::vsnprintf (0, 0, format, args));
    
        std::vsnprintf (&retval[0], retval.size () + 1, format, args);
        va_end (args);
    
        return retval;
    }
    

    Das mag zwar einige Probleme von printf() beheben, aber lange nicht alle (wenig verwunderlich, solange VarArgs verwendet werden). Vor allem ist der Code nicht typsicher. Man kann immer noch Nicht-POD-Objekte übergeben. Du hast auch keine Möglichkeit, die Typen der übergebenen Objekte zur Laufzeit zu erkennen, falsche Formatflags führen nach wie vor zu undefiniertem Verhalten. Der Code ist nicht erweiterbar für eigene Typen.

    Auch wenn die C++-Streams bei Weitem nicht ideal sind, zumindest diese Dinge machen sie besser. Zudem davon ermöglichen Streams im Gegensatz zu printf() und str_printf() Typsicherheit zur Kompilierzeit, was manchmal ein nicht zu unterschätzender Vorteil ist. Aber das hast du ja auf deiner Seite selbst erwähnt.



  • Nexus schrieb:

    Man kann immer noch Nicht-POD-Objekte übergeben.

    Laut Standard nicht. MSVC mit Extensions erlaubt es allerdings, und GCC gibt nur ne Warning.



  • IMHO ist es laut Standard nur UB, und das auch nur in einem potentiell evaluierten Kontext (in Metaspielereien werden auch oft Ellipsen verwendet, da der Code dort aber zur Laufzeit nicht ausgeführt wird, ist das alles wohldefiniert).



  • Nexus schrieb:

    Das mag zwar einige Probleme von printf() beheben, aber lange nicht alle (wenig verwunderlich, solange VarArgs verwendet werden). Vor allem ist der Code nicht typsicher. Man kann immer noch Nicht-POD-Objekte übergeben. Du hast auch keine Möglichkeit, die Typen der übergebenen Objekte zur Laufzeit zu erkennen, falsche Formatflags führen nach wie vor zu undefiniertem Verhalten. Der Code ist nicht erweiterbar für eigene Typen.

    Wenn du auf meiner Seite noch ein wenig weitergelesen hättest, wäre dir vielleicht aufgefallen, daß Typsicherheit sehr wohl erreichbar ist 😉

    Nexus schrieb:

    Auch wenn die C++-Streams bei Weitem nicht ideal sind, zumindest diese Dinge machen sie besser. Zudem davon ermöglichen Streams im Gegensatz zu printf() und str_printf() Typsicherheit zur Kompilierzeit, was manchmal ein nicht zu unterschätzender Vorteil ist. Aber das hast du ja auf deiner Seite selbst erwähnt.

    Das ist richtig - aber die Verwendung von Formatstrings (und damit die Internationalisierbarkeit) und die Typsicherheit zur Übersetzungszeit schließen einander prinzipbedingt aus. GCC kann zwar Formatstrings zur Übersetzungszeit parsen, aber wenn der eigentliche String von gettext kommt, hilft das auch nichts. Die einzig verbleibende Option ist Typsicherheit zur Laufzeit - und die bekommt man mit 3rd-party-Lösungen wie String::Format() oder boost.format, oder auch für printf() und str_printf(), indem man den Workaround auf meiner Seite anwendet.



  • ipsec schrieb:

    IMHO ist es laut Standard nur UB, und das auch nur in einem potentiell evaluierten Kontext (in Metaspielereien werden auch oft Ellipsen verwendet, da der Code dort aber zur Laufzeit nicht ausgeführt wird, ist das alles wohldefiniert).

    Hm. Da wirst du Recht haben 🙂



  • Dann sag das doch gleich 😉
    Ich hätte gedacht, daß gettext ein bekanntes oder zumindest ein googletaugliches Stichwort ist. Aber bitte: mit Lokalisierung alias Internationalisierung meine ich das Bereitstellen von Software in mehreren Anwendersprachen. Gibt man einen Format-String wie den obigen aus, so wird man ihn in mehreren Sprachen bereitstellen wollen. Und spätestens da ist die Stream-Lösung völlig unpraktikabel; ich verweise für ein Beispiel auf den erwähnten Artikel.

    Also ich habe keinerlei Probleme mit der Internationalisierung da ich eine eigene Internationalsisierungsfunktionen eingebaut habe. Und da sehe ich keinerlei Unterschied ob ich nun C++ Streams oder Formatstring benutze. 🙂



  • Aber die Reihenfolge der Wörter! Wie löst Du das, wenn die Satzstellung in ner Sprache anders ist? Wenn Du den Text auf mehreren Sprachen pflegst und über %text1 halt die Positionen der einzufügenden Begriffe wählen kannst, passt das. Bei Streams ist aber die Stellung der variablen Texte fixiert.

    Bei Streams müsste man einen Vermittler bauen, der einen gestreamten Text erkennt, weiß, was davon dynamisch ist, und es entsprechend verdreht, wenn die Sprache geändert wird. Äußerst aufwendig. Es sei denn, man hängt halt wieder einen String rein, der nochmal analysiert und umgeschmissen wird, aber dann ist das ja keine wirkliche Stream-Lösung.

    Hm... Oder man baut einen ganz neuen Stream, bei dem die Reihenfolge der Elemente von vornerein geändert wird. Beispielsweise hätten wir eine Logik, dass:

    ("Text1" "Variable" "Text2") auf Deutsch in der Reihenfolge 123 stehen, auf Englisch in der Reihenfolge 231.

    international_stream™ << "Die Variable " << var << " ist undefiniert.";
    std::cout << international_stream™;
    

    Intern hat international_stream™ jetzt einen Speicher. Der erkennt, dass "Die Variable " zu dem oben angegeben Tripel gehören. Jetzt muss das Ding nur noch den Unterschied zwischen der Variable und dem Text erkennen. Das kann man über Typangaben lösen... Falls nicht, bräuchte man Manipulatoren:

    international_stream™ << text1 << "Die Variable" << var1 << var << text2 << " ist undefiniert.";
    

    oder man gibt ein paar Pattern:

    international_stream™ << text_var_text << "Die Variable " << var1 << " ist undefiniert.";
    

    Und dann haut der Stream das alles um, wenn er um die Ausgabe an cout gebeten wird. Blöd ist aber dennoch die Fragmentierung. Vll. gibt es auf irgend einer Sprache nicht links und rechts nen Text? Schlimmer: Vll. gibt es in der Zielsprache drei Fragmente. Letztlich ist man mit den Streams nicht so dynamisch.

    Aber die Typsicherheit lässt sich doch sicher auch mit Format-Strings lösen, richtig?



  • Ach verflucht! Hast Recht. 🙂

    Bei Formatstring macht man es einfach:

    sprintf(Message, "Person %s nicht erwünscht!", Name);
    sprintf(Message, "Person %s is a fucking terrorist!", Name);
    

    Bei Streams müsste man einen Vermittler bauen, der einen gestreamten Text erkennt, weiß, was davon dynamisch ist, und es entsprechend verdreht, wenn die Sprache geändert wird. Äußerst aufwendig. Es sei denn, man hängt halt wieder einen String rein, der nochmal analysiert und umgeschmissen wird, aber dann ist das ja keine wirkliche Stream-Lösung.

    Genau auch deswegen die Frage. Denn die einfachste Lösung wäre ja ein sprintf für std::string. Aber dank audacia habe ich jetzt auch eine Lösung.

    Message = str_printf("Person %s nicht erwünscht!", Name);
    Message = str_printf("Person %s is a fucking terrorist!", Name);
    


  • audacia schrieb:

    Wenn du auf meiner Seite noch ein wenig weitergelesen hättest, wäre dir vielleicht aufgefallen, daß Typsicherheit sehr wohl erreichbar ist 😉

    Typsicherheit ist mit Ellipsen grundsätzlich niemals erreichbar! Alles andere ist Selbstbetrug.

    Die Ursache liegt darin, daß bei Ellipsen eine variable Anzahl an Argumenten übergeben werden kann, und man durch den Compiler bzw. Linker keinerlei Chance hat, dies jemals sicher zu prüfen. Die ganzen C Format Funktionen können vom Compiler nur dann auf Übereinstimmung geprüft werden, wenn die Formatstrings als Konstanten im Aufruf vorliegen. Ist das nicht der Fall wird selbst diese Einfachprüfung ausgehebelt. Irgend welche Templatespielereien ändern an diesem grundsätzlichen Problem rein gar nichts. Wenn man die Prüfung gar zur Laufzeit verschiebt, hat man das Problem, daß man schon undefined behavior hat, wenn die Zahl der Argumente nicht mit der Anzahl an Formatstringeinträgen übereinstimmt, und somit die Prüfung bereits sinnlos geworden ist.

    Was den Themenkomplex Übersetzungen von Software betrifft. Es funktioniert nur mit Einschränkungen einfach die Texte zu übersetzen, die Satzstellung und die Regeln für die Typographie sind in den Sprachen unterschiedlich. Am besten ist in so einem Fall ein polymorphes Verhalten der Ausgabefunktion, so daß man die Ausgabe auch wirklich an alle Erfordernisse der jeweiligen Sprache anpassen kann.



  • ~john schrieb:

    audacia schrieb:

    Wenn du auf meiner Seite noch ein wenig weitergelesen hättest, wäre dir vielleicht aufgefallen, daß Typsicherheit sehr wohl erreichbar ist 😉

    Typsicherheit ist mit Ellipsen grundsätzlich niemals erreichbar! Alles andere ist Selbstbetrug.

    Deswegen verwendet audacia ja auch Templates. Wenn du seinen den Artikel gelesen hättest, wüsstest du das 😉



  • Warum kann man eigentlich nicht einfach

    #include <cstdio>
    

    einbinden und dann einfach mit sprintf() 'höchstpersönlich' arbeiten?
    Was spricht denn da dagegen, dass man einfach eine schon längst
    vorhandene Standard-Funktion (wie z.B. sprintf()) aufruft ?
    Genau dazu ist ja so eine Funktion eigentlich da, damit man sie nicht
    ständig neu erfinden muss, wenn man sie mal benötigt !!
    Oder seh ich da was falsch ?



  • ExperteOhneWerte schrieb:

    Warum kann man eigentlich nicht einfach

    #include <cstdio>
    

    einbinden und dann einfach mit sprintf() 'höchstpersönlich' arbeiten?
    Was spricht denn da dagegen, dass man einfach eine schon längst
    vorhandene Standard-Funktion (wie z.B. sprintf()) aufruft ?
    Genau dazu ist ja so eine Funktion eigentlich da, damit man sie nicht
    ständig neu erfinden muss, wenn man sie mal benötigt !!
    Oder seh ich da was falsch ?

    Ich weiß auch nicht was dagegen spricht, aber vielleicht sind wir ja nur dumm.

    std::string a("test lol");
    printf("%s", a.c_str());
    

    Ein temporärer String wird so oder so mit den Streamklassen auch entstehen. Weshalb ich die Ganzen Antworten nicht nachvollziehen will.


Anmelden zum Antworten