Code für Ausgabe vereinfachen.



  • Hi!
    Ich möchte Ausgaben aus unterschiedlichen Datentypen erzeugen.

    Soweit habe ich es schon:

    string mk_str ( stringstream& stream )
    {
        return stream.str();   
    }
    stringstream stream;
    stream << "a" << 2 << "b" << 3.3 << endl;
    cout << mk_str(stream);
    

    Schöner wäre es, wenn ich daraus eine Zeile machen könnte.
    Ich habe

    cout << mk_str(stream << "a" << 2);
    

    probiert, aber es kommt der Fehler

    Konvertierung des Parameters 1 von 'std::basic_ostream<_Elem,_Traits>' in 'std::stringstream &' nicht möglich

    Muss ich das mit ner Ellipse machen oder so?



  • Mach doch einfach cout << "a" << 2 << "b" << 3.3 << endl; 😕



  • Das geht leider nicht, es gibt eine 'zentrale' Ausgabefunktion, die einen 'fertigen' string erwartet.



  • print0r schrieb:

    Das geht leider nicht, es gibt eine 'zentrale' Ausgabefunktion, die einen 'fertigen' string erwartet.

    Zeig doch mal! Was macht eine "zentrale Ausgabefunktion"? Die gibt doch auch nur den String aus ... 😕



  • Ja, genau, die zentrale Augabefunktion holt die Übersetzung des String und leitet den String weiter:

    void print(string s)
    {
      string msg = get_message(s); // Übersetzung holen.
    #ifdef LINUX 
    cout << s; // Ausgabe Linuxkonsole
    #endif
    
    #ifdef WINDOWS
    print_rich_edit_control(s); // Ausgabe Windows.
    #endif
    
    }
    

    Ich portiere mein Projekt von C nach C++, in C habe ich die Aufgabe so gelöst:

    char msgbuf[1024];
    char* msg_mk ( char* a, ... )
    {
    	va_list v;
    	va_start(v,a);
    	vsnprintf ( msgbuf, sizeof(msgbuf) / sizeof(*msgbuf) -1, a, v );
    	va_end(v); 
    	return msgbuf;
    }
    print( msg_mk ( "%s%d%s%d%s\n", "Cr", 3, "at", 0, "r" ));
    

    Ich dachte mir, von der Codeästhetik her betrachtet, wäre es besser von char* auf string zu wechseln.



  • Im obigen Bespiel müssen die zeilen natürlich

    #ifdef LINUX 
    cout << msg; // Ausgabe Linuxkonsole 
    #endif 
    
    #ifdef WINDOWS 
    print_rich_edit_control(msg); // Ausgabe Windows. 
    #endif
    

    heißen. Ist aber für mein Problem nicht relevant.



  • Du könntest ein template schreiben:

    template<typename T>
    std::string str(T typ)
    {
        stringstream str;
        //Umwandeln ...
    }
    

    Und dies deiner Funktion mit Variabler Anzahl von parametern übergeben:

    print(int Anzahl, .... )
    {
       //Hier wie printf arbeiten, nur dass der Typ der Argumente (string) bekannt ist
    }
    
    //So dann benutzen:
    print( str(3.0), "Hallo", str(123) );
    

    Wie wär das? 😕

    //Edit: Audacia macht es natürlich sehr viel schöner. 😞



  • print0r schrieb:

    Ich portiere mein Projekt von C nach C++, in C habe ich die Aufgabe so gelöst:

    char msgbuf[1024];
    char* msg_mk ( char* a, ... )
    {
    	va_list v;
    	va_start(v,a);
    	vsnprintf ( msgbuf, sizeof(msgbuf) / sizeof(*msgbuf) -1, a, v );
    	va_end(v); 
    	return msgbuf;
    }
    print( msg_mk ( "%s%d%s%d%s\n", "Cr", 3, "at", 0, "r" ));
    

    Ich dachte mir, von der Codeästhetik her betrachtet, wäre es besser von char* auf string zu wechseln.

    Sicherlich, aber dann verwende doch so etwas, anstatt dich mit Streams herumzuquälen:

    std::string str_printf (const char* format, ...)
    {
        std::va_list args;
        std::string retval;
    
        va_start (args, format);
        retval.resize (std::vsnprintf (0, 0, format, args));
        std::vsnprintf (&retval[0], retval.size () + 1, format, args);
        va_end (args);
    
        return retval;
    }
    


  • ... oder so:

    #include <iostream>
    #include <string>
    #include <sstream>
    
    /// anything to string
    class a2str
    {
      std::stringstream ss;
    public:
      template<typename T>
      a2str& operator<<(T const& x) { ss<<x; return *this; }
      operator std::string() const { return ss.str(); }
    };
    
    int main()
    {
      int i = 23;
      std::string s = a2str() << "mein i ist " << i << ".";
      std::cout << s << '\n';
    }
    

    Gruß,
    SP



  • Pigeon schrieb:

    Du könntest ein template schreiben [...] Und dies deiner Funktion mit Variabler Anzahl von parametern übergeben:

    template<typename T>
    std::string str(T typ);
    
    // ...
    
    print(int Anzahl, ... )
    {
       // Hier wie printf arbeiten, nur dass der Typ der Argumente
       // (string) bekannt ist
    }
    

    Das mit den variadischen Funktionen funktioniert nur für PODs (plain old data structures). std::string ist kein POD. Dieses Sprachmerkmal ("...") ist sooo unattraktiv, dass ich gar nicht weiß, wie man es richtig benutzen müsste. :p

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Das mit den variadischen Funktionen funktioniert nur für PODs (plain old data structures). std::string ist kein POD. Dieses Sprachmerkmal ("...") ist sooo unattraktiv, dass ich gar nicht weiß, wie man es richtig benutzen müsste. :p

    Gruß,
    SP

    😞 Wieder was gelernt. 😞



  • Pigeon schrieb:

    //Edit: Audacia macht es natürlich sehr viel schöner. 😞

    Mach dir nix draus, auf den Code von Audacia wäre ich auch in meinen kühnsten Träumen nicht gekommen!
    Vor allem nicht auf die Zeilen:

    retval.resize (vsnprintf (0, 0, format, args)); 
    	vsnprintf (&retval[0], retval.size () + 1, format, args);  :open_mouth:
    

    @Audacia:

    cout << str_printf ( "%s%d%s\n", "str_printf ist ", 1, "A!" );
    

    🙂

    @Sebastian Pizer
    Ebenfalls ne 1A Variante, die ich (noch nicht ganz) kapiere aber die ich so benutzen kann:

    cout << (s = a2str() << "Meine " << 6 << " Frauen sind super!")
    ;
    

    🙂

    Pigeon schrieb:

    😞 Wieder was gelernt. 😞

    Warum machst du so ein Gesicht? Freu dich doch, das du dazugelernt hast. 😉
    Das nächste mal bist du derjenige, der die beste Lösung vorschlägt.
    Würde ich jedes mal so ein Gesicht machen, wenn ich etwas dazulerne, wäre mein Kinn schon blutig gescheuert vom auf dem Boden schleifen. :p

    So, ich muss jetzt erstmal leider Feierabend machen und ab gehts zum Baden in die Sonne!
    Vielen Dank allen beteiligten! 👍
    🙂



  • Sebastian Pizer schrieb:

    Das mit den variadischen Funktionen funktioniert nur für PODs (plain old data structures). std::string ist kein POD. Dieses Sprachmerkmal ("...") ist sooo unattraktiv, dass ich gar nicht weiß, wie man es richtig benutzen müsste. :p

    Da muss ich mich anschliessen. Mir gefällt aber audacias Code auch sonst nicht besonders... 😉

    Was spricht gegen eine Klasse, die den operator<< anbietet und dessen Aufrufe an die Systemfunktionen weiterleitet?

    struct endl_t {} endl; // führt zum Weiterleiten ans System
    
    class streamer
    {
        public:
            template <typename T>
            streamer& operator<< (const T& value)
            {
                m_stream << value;
                return *this;
            }
    
            streamer& operator<< (const endl_t&)
            {
                system_output(m_stream.str());
                m_stream.str("");
                return *this;
            }
    
        private:
            void system_output(const std::string& message)
            {
                // tue je nach System etwas anderes
            }
    
        private:
            std::stringstream m_stream;
    };
    

    Anwendung:

    streamer s;
    s << "Hallo" << 43 << endl;
    


  • Nexus schrieb:

    Mir gefällt aber audacias Code auch sonst nicht besonders... 😉

    Wenn du etwas konkreter wärest, könnte man fast meinen, daß du auch einen Grund dafür hättest.



  • Muss man das noch begründen?

    Ok, wie wär's mit:
    - VarArgs sind typunsicher
    - VarArgs funktionieren nur für PODs
    - VarArgs erlauben dir hier nicht, beliebige benutzerdefinierte Typen T zu unterstützen, für die es einen operator<<(ostream&, const T&) gibt.



  • audacia schrieb:

    Nexus schrieb:

    Mir gefällt aber audacias Code auch sonst nicht besonders... 😉

    Wenn du etwas konkreter wärest, könnte man fast meinen, daß du auch einen Grund dafür hättest.

    Der Code ist sicherlich gute für seinen bestimmten Anwendungszweck, lässt für den "modernen" Einsatz aber vermissen, eigene Objekte zu verwenden, bei denen z.B. ostream& operator<<( ostream& o, const T& elem ) überladen ist.

    Den Anwendungsfall des OP gibt es ja in mehreren Projekten, da ziehe ich eigentlich immer einen eigenen streambuf für cout/cerr vor, der bei einem flush (also auch endl) den Text überträgt/behandelt. Das eignet sich nur leider nicht für Internationalisierung à la gettext.
    Internationalisierung hatte ich sowieso erst in einem einzigen Projekt (und dann noch in einem privaten), da hatte ich das etwa so gelöst:

    // Im Code:
    std::string error_text = makeError( "BlaNotFound", obj1, obj2, obj3, ... );
    
    // In der Sprach-Datei (.txt-Datei):
    BlaNotFound: Object %1 could not be found in area %3 where %2=%4
    ...
    

    "makeError" hat die Objekte dann per stringstream in Strings umgewandelt und in den Sprach-String eingefügt.

    edit: Alter, über 5 Minuten für einen Post 😕



  • Sebastian Pizer schrieb:

    Muss man das noch begründen?

    Daß "echte C++-Programmierer" variadische Funktionen als Teufelszeug empfinden, ist mir bekannt. Interessiert wäre ich eher an einer Erklärung des "auch sonst".

    Sebastian Pizer schrieb:

    - VarArgs sind typunsicher

    Es ist aber problemlos möglich, die Funktionen z.B. im Debug-Build durch typsichere zu ersetzen.

    Sebastian Pizer schrieb:

    - VarArgs funktionieren nur für PODs
    - VarArgs erlauben dir hier nicht, beliebige benutzerdefinierte Typen T zu unterstützen, für die es einen operator<<(ostream&, const T&) gibt.

    Das sind die klassischen Argumente für C++-Streams. Nur sind C++-Streams trotz aller Vorteile in der Theorie IMHO die schlechteste aller Alternativen; mit z.B. boost::format gibt es deutlich sinvollere.

    Es hat schon einen Grund, daß keine Sprache außer C++ sich auf so etwas abstruses wie diese Streams einläßt. In allen vernünftigen Sprachen existiert printf() oder eine vergleichbare Alternative.

    Badestrand schrieb:

    Der Code ist sicherlich gute für seinen bestimmten Anwendungszweck

    Eben. Darum ging es.



  • audacia schrieb:

    Daß "echte C++-Programmierer" variadische Funktionen als Teufelszeug empfinden, ist mir bekannt. Interessiert wäre ich eher an einer Erklärung des "auch sonst".

    Ich sagte extra "mir gefällt" und nicht etwa "audacias Code ist", es handelt sich also um eine subjektive Ansicht. Ich bin nicht unbedingt ein Fan von den C-Formatfunktionen und VarArgs. Aber auch das finde ich beispielsweise nicht allzu toll, da du hier davon ausgehst, dass der std::string an einem Stück sein muss, was nicht garantiert ist:

    &retval[0]
    

    Und std::vsnprintf ist nicht wirklich im Standard, sondern eher eine sicherere Version von std::vsprintf , oder?

    audacia schrieb:

    Nur sind C++-Streams trotz aller Vorteile in der Theorie IMHO die schlechteste aller Alternativen; [...]

    Es hat schon einen Grund, daß keine Sprache außer C++ sich auf so etwas abstruses wie diese Streams einläßt. In allen vernünftigen Sprachen existiert printf() oder eine vergleichbare Alternative.

    Ja, so langsam wissen wir, dass dir die C++-Streams nicht gefallen. Du musst sie trotzdem nicht so darstellen, als ob man kaum mit ihnen arbeiten könnte, als ob sie die reinste Hölle wären. Sie sind vielleicht nicht der Höhepunkt der Ein-/Ausgabeprogrammierung, aber als so schlimm empfinde ich sie bei weitem nicht. Ich persönlich finde es sogar angenehmer, mit überladenem operator<< zu arbeiten. Mir sind Typsicherheit und leichte Erweiterbarkeit für eigene Typen auch wichtig. Klar sehe ich auch ein, dass die Streams einige Nachteile haben, aber diese sind momentan nicht derart bedrückend, dass ich lieber auf dem C-Level arbeite. Das ist natürlich Geschmackssache und hängt von den jeweiligen Anforderungen ab...



  • Nexus schrieb:

    Aber auch das finde ich beispielsweise nicht allzu toll, da du hier davon ausgehst, dass der std::string an einem Stück sein muss, was nicht garantiert ist

    ISO/IEC 14882 schrieb:

    §21.3.4 basic_string element access
    `const_reference operator[](size_type pos) const;

    reference operator[](size_type pos);Returns: Ifpos < size(), returnsdata()[pos]. Otherwise, ifpos == size(), the const version returnscharT()` . Otherwise, the behavior is undefined.

    Da basic_string::data() einen Zeiger zurückgibt, impliziert das, daß die Daten linear vorliegen. Es sei natürlich denn, ich übersehe etwas.

    Nexus schrieb:

    Und std::vsnprintf ist nicht wirklich im Standard, sondern eher eine sicherere Version von std::vsprintf , oder?

    Soweit ich weiß, ist sie Teil von C99, und zwar ein Teil, den fast alle C++-Compiler implementieren.

    Nexus schrieb:

    Ja, so langsam wissen wir, dass dir die C++-Streams nicht gefallen. Du musst sie trotzdem nicht so darstellen, als ob man kaum mit ihnen arbeiten könnte, als ob sie die reinste Hölle wären.

    Du beliebst maßlos zu übertreiben. Ich bemängele lediglich, daß sie stets als unumgänglich bessere Alternative dargestellt werden.

    Nexus schrieb:

    Das ist natürlich Geschmackssache und hängt von den jeweiligen Anforderungen ab...

    So ist es. Die Anforderung war hier die einfache Portierung von C-Code.


  • Administrator

    audacia schrieb:

    Daß "echte C++-Programmierer" variadische Funktionen als Teufelszeug empfinden, ist mir bekannt. Interessiert wäre ich eher an einer Erklärung des "auch sonst".

    Ich bin doch ein "echter C++ Programmierer", zumindest schimpft man mich immer wieder als viel zu C++ treu, und ich könnte nicht ohne variadische Funktionen leben!
    Wieso? Na deswegen:

    typedef char False;
    typedef struct { False dummy[2]; } True;
    
    template<typename FromT, typename ToT>
    struct IsConvertible
    {
    private:
      True test(ToT const&);
      False test(...); // <---------
      FromT make();
    
    public:
      static bool const RESULT = sizeof(True) == sizeof(test(make()));
      static bool const IS_SAME = false;
    };
    
    template<typename SameT>
    struct IsConvertible<SameT, SameT>
    {
      static bool const RESULT = true;
      static bool const IS_SAME = true;
    };
    

    😃

    Für etwas anderes, würde ich allerdings echt keine variadische Funktionen nehmen, also um solche Funktionen richtig aufzurufen. Ist mir viel zu unsicher. Ich habe sie ein paar Male verwenden wollen und immer ist mir irgendwo ein Fehler unterlaufen. Fängt bei kleinen Dingen an, wie zum Beispiel:

    void fooFloats(std::size_t count, ...)
    {
      va_list list;
      va_start(list, count);
    
      for(std::size_t a = 0; a < count; ++a)
      {
        float f = va_arg(list, float);
        // mach was mit f.
      }
    
      va_end(list);
    }
    
    // ...
    fooFloats(5, 3.2, 234.0, 23, 23.0);
    // Alle Argumente sind falsch.
    

    Falsche Anzahl und (double, double, int, double), statt (float, float, float, float). Ein Fehler passiert einfach viel zu schnell. Aber wer mit dem Risiko leben kann ...

    Da würde ich es eher über Proxies lösen, wie es auch Boost.Format gemacht hat.
    Variadische Funktionen werde ich erst mit dem nächsten Standard anfangen zu verwenden, da man diese dann über Variadic Templates typsicher machen kann.

    audacia schrieb:

    Da basic_string::data() einen Zeiger zurückgibt, impliziert das, daß die Daten linear vorliegen. Es sei natürlich denn, ich übersehe etwas.

    Die Daten liegen zwar linear in diesem Pointer, aber wohin der Pointer zeigt, ist nicht definiert. Grundsätzlich darf hier ein neues Array angelegt werden, welches linear ist, während die interne Repräsentation nicht linear ist.

    Dazu kannst du zum Beispiel dies hier lesen:

    References, pointers, and iterators referring to the elements of a basic_string sequence may be invalidated by the following uses of that basic_string object:
    — As an argument to non-member functions swap() (21.3.7.8), operator>>() (21.3.7.9), and getline() (21.3.7.9).
    — As an argument to basic_string::swap().
    — Calling data() and c_str() member functions.
    — Calling non-const member functions, except operator[](), at(), begin(), rbegin(), end(), and rend().
    — Subsequent to any of the above uses except the forms of insert() and erase() which return iterators, the first call to non-const member functions operator[](), at(), begin(), rbegin(), end(), or rend().

    Der zurückgegeben Pointer kann also invalidiert werden, wenn man erneut data aufruft. Also kann es sich um einen anderen Pointer handeln. Ich kenne zwar keine Standardbibliothek, welche dies wirklich so anwendet. Im nächsten Standard wird dies glaub ich sogar korrigiert.

    Grüssli



  • Dravere schrieb:

    audacia schrieb:

    Da basic_string::data() einen Zeiger zurückgibt, impliziert das, daß die Daten linear vorliegen. Es sei natürlich denn, ich übersehe etwas.

    Die Daten liegen zwar linear in diesem Pointer, aber wohin der Pointer zeigt, ist nicht definiert. Grundsätzlich darf hier ein neues Array angelegt werden, welches linear ist, während die interne Repräsentation nicht linear ist.

    Du vergißt, daß ich den Wert der darunterliegenden Zeichens verändern darf. Eine Implementation, bei der meine obige Annahme nicht zutrifft, müßte also so aussehen:

    template <typename CharT>
        class basic_string
    {
    private:
        CharT* actualData;
        CharT* consecutiveData;
        int lastRequestedChar;
    
        void applyCharModifications (void)
        {
            if (lastRequestedChar >= 0)
            {
                actualData[lastRequestedChar] = consecutiveData[lastRequestedChar];
                lastRequestedChar = -1;
            }
        }
    public:
        const CharT* data (void) const
        {
            applyCharModifications ();
            delete consecutiveData;
            consecutiveData = new CharT[size () + 1];
            memcpy (consecutiveData, actualData, sizeof (CharT) * (size() + 1));
            return consecutiveData;
        }
        CharT& operator [] (unsigned idx)
        {
            applyCharModifications ();
            return const_cast <CharT*> (data ())[idx];
        }
        ...
    };
    

    Dravere schrieb:

    Dazu kannst du zum Beispiel dies hier lesen:

    References, pointers, and iterators referring to the elements of a basic_string sequence may be invalidated by the following uses of that basic_string object:
    — As an argument to non-member functions swap() (21.3.7.8), operator>>() (21.3.7.9), and getline() (21.3.7.9).
    — As an argument to basic_string::swap().
    — Calling data() and c_str() member functions.
    — Calling non-const member functions, except operator[](), at(), begin(), rbegin(), end(), and rend().
    — Subsequent to any of the above uses except the forms of insert() and erase() which return iterators, the first call to non-const member functions operator[](), at(), begin(), rbegin(), end(), or rend().

    Wenn ich das recht verstehe, sagt dieser Abschnitt ausdrücklich, daß folgendes zutrifft:

    CharT& c1 = theString[0]; // first call to non-const member function operator [] ()
    theString[1]; // second call - c1 bleibt gültig!
    CharT& c2 = theString[0]; // da c1 fürderhin gültig bleibt, muß &c1 == &c2 sein
    

    Zwar scheint die von dir hervorgehobene Stelle dagegen zu sprechen, weil der Rückgabewert von operator [] () über data() definiert ist, doch mir scheint die explizite Erwähnung des Indexoperators in der Auflistung von höherem Gewicht zu sein, und die postuliert nur beim erstmaligen Aufruf eine Invalidierung vorheriger Referenzen.

    Es ist richtig, daß die Bedingung (&(operator [] (n)) + 1 == &(operator [] (n + 1)) nicht erfüllt sein muß, aber die von dir zitierte Stelle macht selbst meinen obigen Versuch, diesen Fall herbeizuführen, nichtig. Um also unter Erfüllung der zusätzlichen Forderung meine Annahme zu widerlegen, müßte std::basic_string<> für mit verschiedenen Indizes durchgeführte operator [] () -Aufrufe Zeiger auf unterschiedliche Strings zurückgeben, dabei aber beim Aufruf mit demselben Index jedesmal dieselbe Referenz, und sobald z.B. data() aufgerufen wird, sämtliche Änderungen wieder zum tatsächlichen String zusammenführen. Und das ist, da sind wir uns sicherlich einig, dermaßen abstrus, daß man es getrost außer acht lassen kann 😉

    Dravere schrieb:

    Im nächsten Standard wird dies glaub ich sogar korrigiert.

    Hoffentlich. Für std::vector<> korrigierte das ja, wenn ich mich recht erinnere, bereits der 2003er-Standard.


Anmelden zum Antworten