Operator >> überladen



  • Hallo Leute,

    ich würde gern mal den Operator >> für Stringeingaben überladen, damit ich meinen Quelltext einheitlich mit

    cin >>
    

    und

    cout <<
    

    gestalten kann.

    Mein Quelltext sieht da wie folgt aus:

    #include <iostream>
    #include <string>
    
    std::string &operator>>(std::string&, std::string&);
    
    std::string &operator>>(std::string& instring, std::string& outstring)
    {
    	getline(std::cin,instring);
    	return(instring >> outstring);
    }
    
    using namespace std;
    
    main()
    {
    	string test;
    
    	cout << "Eingabe: ";
    	cin >> test;
    	cout << "Ausgabe: " << test << endl;
    }
    

    ich muss doch dem Operator eigentlich sagen, wie er mit dem Ausdruck

    cin >> test;
    

    umgehen muss aber irgendwie liest cin nur bis zum nächsten Leerzeichen ein.
    Liegt das tatsächlich an zin oder ist mein Operator dafür noch nicht richtig überladen?



  • istream hat bereits eine Überladung für string, und deren Verhalten sollte man nicht einfach ändern.

    PS: Die Überladungen sind auch falsch, sie müssten so aussehen:

    std::istream& operator >> (std::istream& stream, my_type& m)
    {
      // ..
      return stream;
    }
    std::ostream& operator >> (std::ostream& stream, const my_type& m)
    {
      // ..
      return stream;
    }
    


  • ugoessner schrieb:

    ich muss doch dem Operator eigentlich sagen, wie er mit dem Ausdruck

    cin >> test;
    

    umgehen muss aber irgendwie liest cin nur bis zum nächsten Leerzeichen ein.

    Dazu benutzt man auch std::getline(std::istream&, std::string&). Der überladene Shift-Operator von std::istream liest nur bis zum nächsten Whitespace ein.



  • ugoessner schrieb:

    ...aber irgendwie liest cin nur bis zum nächsten Leerzeichen ein. Liegt das tatsächlich an zin oder ist mein Operator dafür noch nicht richtig überladen?

    Das liegt nicht an cin , sondern am operator>> . Abgesehen davon wird deine operator>> Funktion nie aufgerufen.



  • Und wie kann ich dann den Operator >> so überladen, dass er bis zum ende der Zeile liest oder geht das gar nicht?
    Danke schon mal.



  • ugoessner schrieb:

    Und wie kann ich dann den Operator >> so überladen, dass er bis zum ende der Zeile liest oder geht das gar nicht?

    Nimm - einfach - meine - Funktion!
    Außerdem kannst du nicht einfach bereits überladene Operatoren neu definieren. DAnn könnte man einen std::string -Wrapper basteln und dann für den den Shift-Operator überladen, aber das ist mehr als nur unnötig.



  • int main()
    {
    	string test;
    	cout << "Eingabe: ";
    	getline(cin,test); // so wirds gemacht.
    	cout << "Ausgabe: " << test << endl;
    }
    

    Wenn du eine komplette Zeile einlesen willst, darfst du kein operator>> verwenden.



  • ugoessner schrieb:

    Und wie kann ich dann den Operator >> so überladen, dass er bis zum ende der Zeile liest oder geht das gar nicht?
    Danke schon mal.

    struct Line
    {
        std::string value;
    
        operator std::string const&()
        {
            return value;
        }
    };
    
    std::istream& operator>>(std::istream& in, Line& out)
    {
        return std::getline(in, out.value);
    }
    
    std::ostream& operator<<(std::ostream& out, Line const& in)
    {
        return out << in.value;
    }
    
    int main()
    {
        Line line;
        std::cin >> line;
        std::cout << line;
    }
    


  • Ethon schrieb:

    ..
    

    Ho! Klaust mir meine Idee, was? 😃



  • Hacker schrieb:

    Ethon schrieb:

    ..
    

    Ho! Klaust mir meine Idee, was? 😃

    Ehrlich gesagt hab ich deinen Post nicht gelesen, hatte nur das Problem auch schon einmal. 😉



  • Ethon schrieb:

    ...

    struct Line
    {
        std::string value;
    
        operator std::string const&()
        {
            return value;
        }
    }; 
    
    int main()
    {
    	Line line;
    	const std::string& ref = (std::string)line; // copy  :confused: 
    }
    

    Wieso bekomme ich hier keine Referenz auf line.value ?



  • const std::string& ref = (std::string const&)line; // nix copy  :bulb: 
    }
    

    Gugelmoser schrieb:

    Wieso bekomme ich hier keine Referenz auf line.value ?

    Bekommst du ja, aber du konstruierst daraus direkt wieder einen String.



  • Gugelmoser schrieb:

    const std::string& ref = (std::string)line; // copy  :confused:
    

    Wieso bekomme ich hier keine Referenz auf line.value ?

    🙄 Das wird ja auch so gemacht.

    const std::string& ref = line;
    

    Danke für den Hinweis Bashar, da bin ich gerade total neben mir gestanden.



  • man kann das getline auch in einen selbstgestrickten Manipulator stecken, dann spart man sich das casten von Line nach std::string. Die Idee ist, dass die Anwendung später so aussieht:

    string ln;
        if( cin >> line( ln ) )
        {  // Zeile nach string 'ln' eingelsen
    

    das ganze sieht in einer einfachen Fassung so aus:

    #include <iostream>
    #include <string>
    
    struct line_reader
    {
        explicit line_reader( std::string& txt ) : m_txt( txt ) {}
        std::string& m_txt;
    private:
        line_reader& operator=( const line_reader ); // unterdrückt ggf. Warnungen des Compilers
    };
    std::istream& operator>>( std::istream& in, line_reader lr )
    {
        return getline( in, lr.m_txt );
    }
    line_reader line( std::string& txt )
    {
        return line_reader( txt );
    }
    
    int main()
    {
        using namespace std;
        for( string ln; cout << ">", cin >> line( ln ) && ln != "q"; )
        {
            cout << "line: [" << ln << "]" << endl;
        }
        return 0;
    }
    

    und für die Profis gibt es noch die Template-Version mit schaltbarer trimm-Funktionalität für alle std::basic_istream's und mit user-defined Abbruchbedingungen:

    #include <iostream>
    #include <string>
    
    template< typename E, typename Traits, typename Pred >
    struct line_reader
    {
        explicit line_reader( std::basic_string< E, Traits >& txt, Pred delim ) : m_txt( txt ), m_pred( delim ) {}
        std::basic_string< E, Traits >& m_txt;
        Pred m_pred;
    private:
        line_reader& operator=( const line_reader ); // unterdrückt ggf. Warnungen des Compilers
    };
    
    namespace lr_detail
    {
        template< typename E >
        bool is_delimiter( E delim, E c )
        {
            return delim == c;
        }
        template< typename E, typename Pred >
        bool is_delimiter( Pred delim, E c )
        {
            return delim( c );
        }
    }
    
    // --   factory functions
    template< typename E, typename Traits, typename Pred >
    line_reader< E, Traits, Pred > line( std::basic_string< E, Traits >& txt, Pred pred )
    {
        return line_reader< E, Traits, Pred >( txt, pred );
    }
    template< typename E, typename Traits >
    line_reader< E, Traits, E > line( std::basic_string< E, Traits >& txt, E delim = '\n' )
    {
        return line_reader< E, Traits, E >( txt, delim );
    }
    
    // --   extractor
    template< typename E, typename Traits, typename Pred >
    std::basic_istream< E, Traits >& operator>>( std::basic_istream< E, Traits >& in, line_reader< E, Traits, Pred > lr )
    {
        std::ios_base::iostate state = std::ios_base::goodbit;
        bool changed = false;
        typename std::basic_istream< E, Traits >::sentry ok( in, true ); // noskipws = true
        if( ok )
        {
            lr.m_txt.erase();
            typename std::basic_string< E, Traits >::size_type last_nospace_idx = 0;
            try
            {
                const typename std::ctype< E >& ctype_ = std::use_facet< typename std::ctype< E > >( in.getloc() );
                for( typename Traits::int_type m = in.rdbuf()->sgetc(); ; m = in.rdbuf()->snextc() )
                {
                    if( Traits::eq_int_type( m, Traits::eof() ) )
                    {
                        state |= std::ios_base::eofbit;
                        break;
                    }
                    const E c = Traits::to_char_type( m );
                    changed = true;
                    if( lr_detail::is_delimiter( lr.m_pred, c ) )
                    {
                        in.rdbuf()->sbumpc(); // consume the delimiter char
                        break;
                    }
                    if( ctype_.is( std::ctype_base::space, c ) && (in.flags() & std::ios_base::skipws) )
                    {
                        if( last_nospace_idx > 0 )
                            lr.m_txt += c;
                    }
                    else
                    {
                        lr.m_txt += c;
                        last_nospace_idx = lr.m_txt.size();
                    }
                }
                if( in.flags() & std::ios_base::skipws )
                    lr.m_txt.erase( last_nospace_idx ); // trim text, if skipws is set
            }
            catch( ... )
            {
                state |= std::ios_base::badbit;
                if( in.exceptions() & std::ios_base::badbit )
                    throw; // re-throw, if badbit is set
            }
        }
        if( !changed )
            state |= std::ios_base::failbit;
        in.setstate( state );
        return in;
    }
    
    struct eol
    {
        bool operator()( char c ) const
        {
            return c == ';' || c == '\n';
        }
    };
    
    int main()
    {
        using namespace std;
        for( string ln; cout << ">", cin >> line( ln /*, eol()*/ ) && ln != "q"; )
        {
            if( ln == "skipws" ) cin >> skipws;
            else if( ln == "noskipws" ) cin >> noskipws;
            else cout << "line: [" << ln << "]" << endl;
        }
        return 0;
    }
    

    .. probiert's mal aus.

    Gruß
    Werner

    PS.: nicht vergessen: getline ist doof



  • Werner Salomon schrieb:

    PS.: nicht vergessen: getline ist doof

    Sorry, aber ich sehe hier keine Begründung. Warum ist getline doof?

    Nehmen wir ein kleines Beispiel. Ich habe eine Konfigurationsdatei, die von meinem Programm eingelesen werden soll. Damit der Benutzer die Datei lesen und bearbeiten kann, werden die Daten zeilenweise in der Form "key = value" gespeichert. Einzelne Zeilen werden mit getline eingelesen und danach geparst.

    Oder ist das eine Aufgabe, die zu deinen Ausnahmen gehört? Was definierst du überhaupt als Anwendungsfall für getline, und was als Ausnahme? Und was macht dein getline besser als std::getline?



  • 314159265358979 schrieb:

    Nehmen wir ein kleines Beispiel. Ich habe eine Konfigurationsdatei, die von meinem Programm eingelesen werden soll. Damit der Benutzer die Datei lesen und bearbeiten kann, werden die Daten zeilenweise in der Form "key = value" gespeichert. Einzelne Zeilen werden mit getline eingelesen und danach geparst.

    Also das finde ich jetzt doof. Nimm eine XML-Datei, lies alles auf einmal ein und parse es dann mit einem XML-Parser. 🤡

    New, im Ernst. Ich versteh auch nicht, was doof an getline ist. Macht was ich will, ist portabel und prüfbar.



  • 314159265358979 schrieb:

    Und was macht dein getline besser als std::getline?

    besser nichts - das ist genauso doof 😉
    Es kann etwas mehr, aber das ist alles.

    314159265358979 schrieb:

    Warum ist getline doof?

    Nehmen wir ein kleines Beispiel. Ich habe eine Konfigurationsdatei, die von meinem Programm eingelesen werden soll. Damit der Benutzer die Datei lesen und bearbeiten kann, werden die Daten zeilenweise in der Form "key = value" gespeichert. Einzelne Zeilen werden mit getline eingelesen und danach geparst.

    wenn Du dies tust, so ignorierst Du den Parser, den Dir C++ zur Verfügung stellt - nämlich den std::istream - liest an ihm vorbei und bastelst Dir anschließend einen eigenen Parser. Dieses Vorgehen ist doch doof, oder?

    Es gibt hier im Forum zu häuf Beispiele dafür. Die Ursache ist i.A. die Unkenntnis über die Fähigkeiten von istream.

    Gruß
    Werner



  • Werner Salomon schrieb:

    314159265358979 schrieb:

    Warum ist getline doof?

    Nehmen wir ein kleines Beispiel. Ich habe eine Konfigurationsdatei, die von meinem Programm eingelesen werden soll. Damit der Benutzer die Datei lesen und bearbeiten kann, werden die Daten zeilenweise in der Form "key = value" gespeichert. Einzelne Zeilen werden mit getline eingelesen und danach geparst.

    wenn Du dies tust, so ignorierst Du den Parser, den Dir C++ zur Verfügung stellt - nämlich den std::istream - liest an ihm vorbei und bastelst Dir anschließend einen eigenen Parser. Dieses Vorgehen ist doch doof, oder?

    Es gibt hier im Forum zu häuf Beispiele dafür. Die Ursache ist i.A. die Unkenntnis über die Fähigkeiten von istream.

    Gruß
    Werner

    Ich persönlich würde darauf ein getline mit '=' als demin und anschließend ein getline mit '\n' als delim loslassen. Dann hat man mit minimalem Aufwand Key und Value in nem String. Oder gibts da eine bessere Vorgehensweise?


  • Mod

    Ethon_ schrieb:

    Ich persönlich würde darauf ein getline mit '=' als demin und anschließend ein getline mit '\n' als delim loslassen. Dann hat man mit minimalem Aufwand Key und Value in nem String. Oder gibts da eine bessere Vorgehensweise?

    Das wäre doof, da man dann den ganzen Whitespace mitnimmt. Besser stream >> key >> gleich >> value;



  • SeppJ schrieb:

    Ethon_ schrieb:

    Ich persönlich würde darauf ein getline mit '=' als demin und anschließend ein getline mit '\n' als delim loslassen. Dann hat man mit minimalem Aufwand Key und Value in nem String. Oder gibts da eine bessere Vorgehensweise?

    Das wäre doof, da man dann den ganzen Whitespace mitnimmt. Besser stream >> key >> gleich >> value;

    Was aber erfordert dass sowohl Key als auch Value keine Whitespace enthalten. Da doch lieber einfach danach trimmen.

    Obwohl meine Lieblingslösung ja wäre, die ganze Datei in einen vector zu laden, beim Parsen Null-Terminatoren einzufügen und einfach nur Pointer auf die Strings herumzureichen. Dann ist es nur eine Allocation.


Anmelden zum Antworten