std::ostream nach std::ostringstream casten



  • Um mir ettliche Aufrufe von lexical_cast zu ersparen, möchte ich alles auf einen ostringstream ausgeben und danach mittels str() einmalig einen String zu erzeugen.
    Wäre ja auch einfach, wenn es nicht als rvalue sein müsste:

    myLog.write( (std::ostringstream() << " Value: " << 12.5f ).str() );
    

    So gehts natürlich nicht, da op<< nur eine Referenz auf ostream zurückliefert.
    Ein cast von diesem ostream zu einem ostringstream will nicht so recht, danach hat
    man nur Datenmüll im String (was wohl auf ein ungültiges Objekt schließen lässt):

    myLog.write( static_cast< std::ostringstream* >( &( std::ostringstream() << " Value: " << 12.5f ) )->str() );
    

    Aber das muss doch möglich sein, schließlich handelt es sich bei dem Objekt ja um ein std::ostringstream, somit sollte ein downcast doch ohne Probleme funktionieren.

    Kennt jemand von euch eine Lösung?

    Falls ihr euch fragt wozu ich das brauche, ich brauche es für meine Log-Einträge, damit ich das alles in ein DebugOutput()-Makro packen kann, ohne dass ich den String davor in einem #ifdef-block erzeugen muss.



  • Eine Möglichkeit wäre es, eine temporäre Variable dafür zu verwenden, deren Inhalt du dann weitergeben kannst.

    Möglichkeit 2: Was für ein Typ ist "myLog" (das sieh nach einem Stream aus)?

    Möglichkeit 3: static_cast und dynamic_cast funktionieren auch mit Referenzen:

    myLog.write( dynamic_cast< std::ostringstream& >( std::ostringstream() << " Value: " << 12.5f )->str() );//ungetestet
    


  • 1. geht nicht, dann kann ich das ja nicht mehr innerhalb eines Funktionsaufrufes reinpacken
    2. ist kein Stream, verwendet intern aber einen Stream zur Ausgabe und nein eine direkte Ausgabe auf den Stream ist nicht möglich
    3. Ob Referenz oder Zeiger ändert nichts daran, dass es fehlschlägt (dynamic_cast schmeißt ne Exception/gibt NULL zurück).





  • Genauso wie CStoll wollte ich es zunächst auch posten, habe es dann aber ausprobiert und es kam "Müll" heraus. Meine Vermutung:

    Das ganze ist undefiniert, da das erzeugte Temporary unmittelbar nach dem Funktionsaufruf, in dem es genutzt wurde gelöscht werden darf. Und jener Funktionsaufruf ist operator<<( ostream&, const char* ). D.h. schon beim zweiten operator<<, spätestens aber beim Cast, existiert der ostringstream schon garnicht mehr. Der Compiler "weiss" ja nicht, dass operator<< genau den ostream wieder zurückgibt, den er erhalten hat...



  • Ja auf diese Schlussfolgerung bin ich auch gekommen, aber habe gehofft es gibt da ne Möglichkeit..

    ..und .filmor Danke für die Erinnerung an boost::format, daran hab ich schon gar nicht mehr gedacht. Das nehm ich 🙂


  • Mod

    LordJaxom schrieb:

    Genauso wie CStoll wollte ich es zunächst auch posten, habe es dann aber ausprobiert und es kam "Müll" heraus. Meine Vermutung:

    Das ganze ist undefiniert, da das erzeugte Temporary unmittelbar nach dem Funktionsaufruf, in dem es genutzt wurde gelöscht werden darf. Und jener Funktionsaufruf ist operator<<( ostream&, const char* ). D.h. schon beim zweiten operator<<, spätestens aber beim Cast, existiert der ostringstream schon garnicht mehr. Der Compiler "weiss" ja nicht, dass operator<< genau den ostream wieder zurückgibt, den er erhalten hat...

    nein. temporaries werden erst am ende eines vollständigen ausdrucks zerstört. tatsächlich funktioniert

    myLog.write( static_cast< std::ostringstream* >( &( std::ostringstream() << " Value: " << 12.5f ) )->str() );
    

    genauso wie es sein muss (und ist alles andere als undefiniert. dass dabei nicht das herauskommt, was man auf den ersten blick erwarten würde, liegt an einem designfehler der iostreams.

    std::ostringstream()
    

    ist ein rvalue; folglich werden in

    std::ostringstream() << " Value: "
    

    überladungen des operators<<, die keine memberfunktionen von ostringstream sind, nicht brücksichtigt (da diese alle eine referenz auf non-const als linken parameter haben). dummerweise sind die standardüberladungen für diesen operator allerdings als memberfunktionen deklariert, und es gibt hier ein match für

    ISO/IEC 14882:2003 27.6.2.1 schrieb:

    basic_ostream<charT,traits>& operator<<(const void* p);
    

    Ab dem zweiten operator << in diesem ausdruck spielt das keine rolle mehr, denn dann ist der linke operand ein lvalue und alle überladungen werden gleich behandelt.

    myLog.write( static_cast< std::ostringstream& >( const_cast< std::ostream& >( static_cast< const std::ostream& >( std::ostringstream() ) ) << " Value: " << 12.5f ).str() );
    

    könnte man nutzen; den code sollte man dann aber lieber niemandem zeigen (je nachdem, wie temporaries an referenzen auf const beim eingesetzten compiler gebunden werden, könnte das sogar undefiniertes verhalten sein) 😉

    edit: einfacher wäre noch der aufruf einer anderen memberfunktion, die in diesem zusammenhang keinen effekt hat. das ist dann in jedem falle auch wohldefiniert:

    myLog.write( static_cast< std::ostringstream& >( std::ostringstream().flush() << "Value: " ).str() );
    

Log in to reply