Typkonvertierung per stringstream, Sonderfall für std::string



  • Hi,

    ich benutze einen stringstream in einer template Funktion zur Typkonvertierung, allerdings funktioniert sie für einen Sondefall nicht. Das Beispiel macht hier keinen Sinn, da eine string zu string Konvertierung nicht nötig sein sollte, aber im konkreten Zusammenhang kann ich damit generalisieren und spare mir die Behandlung von Sonderfällen.

    #include <string>
    #include <stdexcept>
    #include <iostream>
    #include <sstream>
    
    template<typename T>
    T convert( const std::string& input )
    {
       T output;
       stringstream ss;
       ss << input;
       ss >> output;
    
       if( ss.fail() )
       {
          throw runtime_error( "Bämm!" );
       }
       return output;
    }
    
    int main()
    {
       std::string s = convert<string>( "" ); // wirft runtime_error!
    }
    

    Ein leerer string kann offensichtlich nicht in irgendeinen Wertetyp konvertiert werden, aber warum kann ein leerer string nicht zu string konvertiert werden? Die Statusflags aus rdstate() liefern keinen eindeutigen Zustand für verschiedene Konvertierungen, hat da jemand eine Idee, wie ich das hier in den Griff kriegen kann?



  • (Das löst zwar nicht die Frage, aber vielleicht das Problem, sorry):
    Vielleicht willst Du eh für Performance die Konvertierung von string nach string speziell anbieten.

    #include <string>
    #include <stdexcept>
    #include <iostream>
    #include <sstream>
    using namespace std;
    
    template<typename T>
    T convert( const std::string& input )
    {
       T output;
       std::stringstream ss;
       ss << input;
       ss >> output;
    
       if( ss.fail() )
       {
          throw std::runtime_error( "Bämm!" );
       }
       return output;
    }
    template<>
    std::string convert<std::string>( const std::string& input )
    {
       return input;
    }
    
    int main()
    {
       std::cout<<convert<string>( "" )<<'\n';//kein Bäm
    }
    

  • Administrator

    Wenn nix drin ist, dann kann man auch nix auslesen.
    Als Lösung könntest du eine Spezialisierung anbieten. Die dürfte sowieso sinnvoll sein, damit man bei std::string gar nicht über den std::stringstream geht:

    #include <string>
    #include <stdexcept>
    #include <iostream>
    #include <sstream>
    
    template<typename T>
    T convert( const std::string& input )
    {
       T output;
       stringstream ss;
       ss << input;
       ss >> output;
    
       if( ss.fail() )
       {
          throw runtime_error( "Bämm!" );
       }
       return output;
    }
    
    template<>
    std::string convert<std::string>(std::string const& input)
    {
      return input;
    }
    
    int main()
    {
       std::string s = convert<string>( "" ); // wirft keine runtime_error!
    }
    

    Kennst du übrigens lexical_cast aus Boost?

    Grüssli


  • Mod

    Der Operator >> ist sehr komplex programmiert und kann viele tolle Dinge machen. Eine Sache ist, dass er Whitespace komplett ignoriert und beim Lesen in einen String versucht das erste Wort (definiert als etwas was kein Whitespace ist, abgetrennt durch Whitespaces oder das Ende des Strings) zu finden. Wenn er kein solches Wort findet, dann ist das ein Fehler. Wenn dies nicht das Verhalten ist, welches du für String wünscht, dann musst du deine Funktion spezialisieren.



  • Hui, das ging ja fix.

    Als ich "Absenden" geklickt habe fiel mir auch lexical_cast ein, das benutze ich jetzt und es funktioniert tadellos.
    Eine Spezialisierung wäre natürlich möglich, aber ich hatte gehofft, dass ich ohne Sonderfallbehandlung auskomme. Die Frage, warum ein leerer stringstream keinen leeren string erzeugen hat natürlich weiterhin Bestand. Wenn ein leerer String einen gültigen Zustand eines strings darstellt, warum kann ein leerer stringstream dann keinen leeren string "befüllen"? Die Behandlung dieses Falles hätte ich im >> Operator vermutet, sagt der Standard irgendetwas darüber?


  • Mod

    DocShoe schrieb:

    Hui, das ging ja fix.

    Als ich "Absenden" geklickt habe fiel mir auch lexical_cast ein, das benutze ich jetzt und es funktioniert tadellos.
    Eine Spezialisierung wäre natürlich möglich, aber ich hatte gehofft, dass ich ohne Sonderfallbehandlung auskomme. Die Frage, warum ein leerer stringstream keinen leeren string erzeugen hat natürlich weiterhin Bestand. Wenn ein leerer String einen gültigen Zustand eines strings darstellt, warum kann ein leerer stringstream dann keinen leeren string "befüllen"? Die Behandlung dieses Falles hätte ich im >> Operator vermutet, sagt der Standard irgendetwas darüber?

    😕 Das steht doch bei meiner Antwort genau erklärt. Operator >> macht etwas anderes als du denkst. Guck mal in die Referenz davon:
    http://www.cplusplus.com/reference/string/operator>>/
    Wenn das nicht ist was du willst, dann benutz eine andere Leseoperation.



  • Ich habe schon verstanden, was der >> Operator tut, er liest das nächste Wort aus einem istream und ignoriert dabei führende whitespaces. Ich möchte nur wissen, warum ein leerer istream keinen leeren string erzeugen kann. Vermutlich hat der Standard das irgendwo definiert, weil sonst solche Dinge wie

    istream i;
    string s;
    while( i >> s )
    {
       ...
    }
    

    nicht funktionieren. Auf der anderen Seite finde ich meine Frage auch nicht besonders blöde, weil sie sich mit Implementationsdetails auseinandersetzt, die nicht offensichtlich sind und es durchaus Fälle gibt, wo das Standardverhalten nicht gewünscht wird.
    Eine Antwort wie "Das ist im Standard so definiert" hätte mir übrigens gereicht und jede weitere Frage überflüssig gemacht.


  • Administrator

    Lies meine Antwort. Wenn nix drin ist, dann kann nix ausgelesen werden.
    Du sagst dem Stringstream, dass du ein Wort auslesen möchtest. Er schaut in seinem Puffer nach, findet dort nichts mehr und setzt somit das EOF Flag. Und das EOF Flag wird als Fehler gewertet. Was gibt es daran nicht zu verstehen?

    Grüssli



  • Nur weil Input und Ouput beides strings sind, haben sie noch nichts miteinander zu tun. Wenn "Hallo Welt" im stringstream steht, bekommst du durch stream >> str auch nur das "Hallo".



  • Ich habe den Eindruck, dass wir aneinander vorbeireden. Es geht doch schon längst nicht mehr um die Frage, wie ich diesen Sonderfall behandle, sondern nur noch darum, warum dieser Fall so behandelt wird, wie er behandelt wird.

    Es gibt zwei Herangehensweisen:

    1. std::string wird genauso behandelt wie alle anderen Datentypen auch, es kann nur dann ein gültiger string gelesen werden, wenn tatsächlich etwas im istream steht, was kein whitespace ist. Ist so, weil es im Standard so definiert ist.

    2. Ein std::string darf leer sein. Demzufolge könnte man annehmen, dass das Lesen eines strings aus einem leeren istream einen leeren string zurückgibt, ohne einen Fehlerstatus zu setzen. Ist nicht so, weil es im Standard anders definiert ist.

    Ich habe mich jetzt nur gewundert, warum 2) nicht gilt. Meine vermutete Antwort ist: "Weil es der Standard so definiert".



  • Michael E. schrieb:

    Nur weil Input und Ouput beides strings sind, haben sie noch nichts miteinander zu tun. Wenn "Hallo Welt" im stringstream steht, bekommst du durch stream >> str auch nur das "Hallo".

    Das ist ein wirklich wichtiger Einwand! Glücklicherweise liefert lexical_cast tatsächlich den kompletten string zurück und nicht nur das erste Wort.


  • Administrator

    Diese Spezialbehandlung würde doch überhaupt gar keinen Sinn machen. Wenn nichts mehr ausgelesen werden kann, kann auch nichts mehr ausgelesen werden. Das Ende des Streams ist erreicht. Wer möchte schon leere "Wörter" auslesen? Wenn ich Zahlen aus einem File lesen möchte, schreibe ich:

    int input;
    std::vector<int> vec;
    
    while(stream >> input) vec.push_back(input);
    

    Wenn ich Wörter auslesen möchte, schreibe ich:

    std::string input;
    std::vector<std::string> vec;
    
    while(stream >> input) vec.push_back(input);
    

    Mit deiner Spezialbehandlung würde das Letzte in einer Endlosschleife enden. Es würde die Logik und Konsistenz völlig brechen.

    Grüssli



  • Wie sinnvoll etwas ist hängt vom Kontext ab. Manchmal sind Dinge optional (Kommentare, Passwörter, Adresszusätze, etc.).
    Es musste irgendwie definiert werden, weil beide Verhalten ihre Vor- und Nachteile haben, und dann hat man sich für das Verhalten entschieden, das mit den anderen Datentypen konform geht.

    btw:
    Eine Möglichkeit für diesen Fall wäre das failbit und badbit zu löschen und goodbit und eofbit zu setzen, wenn man einen string liest. Aber egal, es ist halt wie es ist und ist so definiert worden. Spielt inzwischen ja dank lexical_cast auch keine Rolle mehr.

    Edit:
    @dravere
    Gebe dir für das letzte Beispiel natürlich völlig recht, hatte ich einem früheren Posting ja auch schon erwähnt.


  • Administrator

    DocShoe schrieb:

    Wie sinnvoll etwas ist hängt vom Kontext ab. Manchmal sind Dinge optional (Kommentare, Passwörter, Adresszusätze, etc.).

    Dafür gibt es aber deutlich bessere Möglichkeiten. Zum Beispiel kannst du da gut mit std::getline(std::istream&, std::string&) arbeiten.
    Ich glaube auch, dass du hier zwei Dinge durcheinander bringst. Das eine ist der Parser und das andere ist der Stream. Ein Stream ist kein Parser. Ein Parser muss je nach Aufbau und Interpretation entscheiden, wie er nun weiter einliest. Ein Stream ist einfach nur ein Fluss von Daten. Sobald dieser Fluss von Daten fertig ist, gibt es nichts mehr zu holen, also ist jede weitere Leseoperation auf dem Stream ein Fehler.

    Grüssli


Anmelden zum Antworten