uint8_t korrekt von istream lesen



  • Ich würde den Umweg über int machen.



  • Ethon schrieb:

    Ich würde den Umweg über int machen.

    Das konsumiert Dir aber u.U. zu viele Zeichen aus dem Stream.



  • Ungefähr so?

    template< typename T > void readNumeric( std::istream &stream, T& val )
    {
    	val = T();
    
    	const T compareMax = std::numeric_limits< T >::max() / 10;
    	const T lastDigitMax = std::numeric_limits< T >::max() % 10;
    
    	int sign = 0;
    
    	while( true )
    	{
    		int c = stream.peek();
    
    		if( !stream.good() )
    		{ return; }
    
    		switch( c )
    		{
    		case '+':
    			//already read an algebraic sign
    			if( sign != 0 )
    			{ return; }
    
    			sign = 1;
    			break;
    
    		case '-':
    			if( ( sign != 0 ) ||						//already read an algebraic sign
    				!std::numeric_limits< T >::is_signed )	//type is unsigned
    			{ return; }
    
    			sign = -1;
    			break;
    
    		default:
    			if( !isdigit( c ) )
    			{ return; }
    
    			c -= '0';
    
    			//Adding another digit would result in an overflow -> break
    			if( val > compareMax )
    			{ return; }
    
    			//Adding another digit might result in an overflow
    			if( ( val == compareMax ) && ( c > lastDigitMax ) )
    			{
    				//If a minus sign was read...
    				if( sign == -1 )
    				{
    					//...the last digit is allowed to be lastDigitMax + 1
    					if( c == ( lastDigitMax + 1 ) )
    					{
    						//Reached the minium!
    						val = std::numeric_limits< T >::min();
    						stream.get();
    					}
    					else
    					{ val *= -1; }
    				}
    				return;
    			}
    
    			val = val * 10 + c;
    
    			break;
    		}
    
    		stream.get();
    	}
    
    	if( sign == -1 )
    	{ val *= sign; }
    }
    

  • Mod

    So, jetzt noch die locales und alle Zahlenformatmanipulatoren unterstützen 😃 .



  • Könntest du nicht einfach einen Wrapper mit Einlese-Operator schreiben, der einen short einliest und dann was wirft, falls die Zahl nicht im richtigen range ist? (Also wie von Ethon vorgeschlagen)? Und wenn Tachyons Einwand, dann einfach nacheinander Drei Zeichen aus dem Stream lesen, in einen Stringstream schieben und von da streamen?



  • #include <iostream>
    #include <sstream>
    
    int main()
    {
        std::stringstream ss("123456789012345678901234567890");
    
        int i;
        ss >> i;
    
        std::cout << ss.rdbuf();
    }
    

    Das hier liest auch alle Zahlen ein. Also verstehe ich immer noch nicht, was das Problem ist, einfach int zu verwenden und den Wert einem uint8_t zuzuweisen.



  • Und was machst du, wenn bei der Konvertierung in einen uint8_t ein Overflow auftreten würde?



  • Das könnte man ja nach dem Einlesen abfangen. Im Normalfall muss man Input sowieso selbst verifizieren, dazu sind die Stream wirklich nicht gedacht. Wenn ich einen uint8_t einlese, habe ich eine ungefähre Ahnung wie der genaue Wertebereich aussehen sollte. Wenn genau 0-255 zulässig ist, dann ist das imho ein Spezialfall.



  • Nein, das kann man nicht nach dem Einlesen abfangen, da im Falle eines Overflows alle Zeichen im Stream bleiben sollen. .putback() ist aber nur für 1 Zeichen nach der letzten Leseoperation definiert.



  • Ethon schrieb:

    Im Normalfall muss man Input sowieso selbst verifizieren, dazu sind die Stream wirklich nicht gedacht.

    das ist so nicht richtig. Streams dienen dazu, aus einer Folge von Zeichen (hier Ziffern) eine Variable eines bestimmten Typs zu belegen (hier uint8_t). Das ist entweder möglich oder eben nicht; und im letzteren Fall sollte der Stream in den Zustand fail gehen.

    Der Normalfall ist, dass für den gewünschten Typ ein entsprechender Streaming-Operator existiert. Egal ob aus dem Standard oder selbst geschrieben. Das Problem ist hier, dass ein uin8_t eben kein Integer im Sinne einer Zahl ist, sondern ein Zeichen. D.h. es existiert bereits ein Streaming-Operator, der aber was anderes tut.
    In solchen Fällen kann man sich immer mit einem Manipulator behelfen.

    Ich skizzier' mal was:

    #include <iostream>
    #include <sstream>
    #include <cstdint> // std::uint8_t
    #include <limits>
    
    struct read_uint8 // Manipulator zum Einlesen von std::uint8_t
    {
        typedef std::uint8_t value_type;
        explicit read_uint8( value_type& target )
            : target_( target )
        {}
        friend std::istream& operator>>( std::istream& in, read_uint8 rdr )
        {
            unsigned i;
            if( in >> i && i > unsigned(std::numeric_limits< value_type >::max()) )
                in.setstate( std::ios_base::failbit ); // boost::numeric_cast wäre hier auch angebracht
            if( in )
                rdr.target_ = value_type( i );
            return in;
        }
    private:
        value_type& target_;
    };
    
    int main()
    {
        using namespace std;
        istringstream in("1 34 255 256 1 egal");
        for( uint8_t i; in >> read_uint8( i ); )
            cout << "Gelesen: " << int(i) << endl;
        in.clear();
        cout << "Rest: [" << in.rdbuf() << "]" << endl;
        return 0;
    }
    

    Gruß
    Werner



  • Also jetzt verstehe ich gar nichts mehr. Ich hätte gedacht, wenn ein Wert zu groß ist, wird der Stream dadurch nicht verändert, offenbar ist das aber doch nicht so. http://ideone.com/VmHXE
    Wieso wird hier die zu große Zahl aus dem Streambuffer gelöscht?



  • 314159265358979 schrieb:

    Also jetzt verstehe ich gar nichts mehr. Ich hätte gedacht, wenn ein Wert zu groß ist, wird der Stream dadurch nicht verändert, offenbar ist das aber doch nicht so. http://ideone.com/VmHXE
    Wieso wird hier die zu große Zahl aus dem Streambuffer gelöscht?

    wenn eine Lesefehler auftritt - und ein 'zu groß' ist auch eine Lesefehler - so wird zumindest das fail-Bit gesetzt, und damit der Stream verändert.

    Die zu große Zahl (hier 256) wird nicht gelöscht. Sie wird interpretiert, als zu groß erkannt und dann verworfen. Natürlich muss sie dazu vorher aus dem Streambuf gelesen werden. Wie Du schon richtig erkannt hast, kann ein streambuf im Normalfall nur ein Zeichen weit 'sehen'.
    Wenn also die Zeichen '2', '5' und '6' im Streambuffer stehen, so ist die Welt nach '2' und '5' noch in Ordnung. Und ein Overflow kann frühestens dann erkannt werden, wenn das nächste Zeichen gelesen wurde; vorher eben nicht. Bei meiner Implementierung wird der Overflow sogar erst dann erkannt, wenn ein kompletter 'unsigned int' eingelesen wurde. Das könnte man noch verbessern, aber die '2' und die '5' müssen gelesen (und konsumiert!) werden, d.h. sie sind im Allgemeinen für weitere Leseoperationen verloren.

    Gruß
    Werner



  • Danke für die Erklärung, das ergibt natürlich Sinn. Ich denke, ich habe es nun verstanden, denn einen solchen Manipulator hätte ich auch noch hinbekommen. 🤡

    Macht es eigentlich einen Unterschied, ob man std::ios::failbit oder std::ios_base::failbit schreibt?



  • Das habe ich doch als Argument dazugesagt, weshalb ich einfach danach prüfen würde, siehe mein Codesnippet. 🙂

    Ob das Ganze mit dem Manipulator schöner wird? Bezweifle es.


Anmelden zum Antworten