stream_iterator discussion

I was playing around with the STL and wrote a CSV file istream_iterator-like class. It is a template so that it can read strings, ints, doubles, etc. Its constructors accept a char delimiter, so that it may be used for more than strictly comma-delimited files. I also added a specialization for strings so that white space characters could be retained.

I would like to hear all questions, comments, suggestions, concerns, feedback, funny stories, dirty jokes, etc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <sstream>
#include <fstream>
#include <iterator>

using namespace std;

template <class T>
class csv_istream_iterator: public iterator<input_iterator_tag, T>
{
    istream * _input;
    char _delim;
    string _value;
public:
    csv_istream_iterator( char delim = ',' ): _input( 0 ), _delim( delim ) {}
    csv_istream_iterator( istream & in, char delim = ',' ): _input( &in ), _delim( delim ) { ++*this; }

    const T operator *() const {
        istringstream ss( _value ); 
        T value;
        ss >> value;
        return value;
    }

    istream & operator ++() {
        if( !( getline( *_input, _value, _delim ) ) )
        {
            _input = 0;
        }
        return *_input;
    }

    bool operator !=( const csv_istream_iterator & rhs ) const {
        return _input != rhs._input;
    }
};

template <>
const string csv_istream_iterator<string>::operator *() const {
    return _value;
}

int main( int argc, char * args[] )
{
    { // test for integers
        ifstream fin( "data.csv" );
        if( fin )
        {
            copy( csv_istream_iterator<int>( fin ),
                  csv_istream_iterator<int>(),
                  ostream_iterator<int>( cout, " " ) );

            fin.close();
        }
    }

    cout << endl << "----" << endl;

    { // test for strings
        ifstream fin( "data.csv" );
        if( fin )
        {
            copy( csv_istream_iterator<string>( fin ),
                  csv_istream_iterator<string>(),
                  ostream_iterator<string>( cout, "|" ) );

            fin.close();
        }
    }

    return 0;
}


Example data.csv:

10, 20, 30,
40 , 50 , 60


Sample output:

10 20 30 40 50 60
----
10| 20| 30|
40 | 50 | 60
|
Last edited on
I took the idea a little farther and created an ostream_iterator-like class that allows the user to specify a formatter via unary function object that takes the value_type as a parameter and returns a string.

Here is the additional code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template<class T, class Formatter>
class formatted_ostream_iterator: public iterator<output_iterator_tag, T>
{
    ostream * _output;
    Formatter f;
public:
    formatted_ostream_iterator(): _output( 0 ) {}
    formatted_ostream_iterator( ostream & output, Formatter formatter ): _output( &output ) {}

    formatted_ostream_iterator & operator =( const T & t ) {
        *_output << f( t );
    }

    formatted_ostream_iterator & operator *() {
        return *this;
    }

    ostream & operator ++() {
        return *this->_output;
    }
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Formatter
{
    static const int columns = 8;
    static const int width = 4;

    long count;
public:
    Formatter(): count( 0 ) {}

    template<class T>
    string operator ()( const T & t )
    {
        ostringstream out;
        out << hex << setw( width ) << t;
        if( !( ++count % columns ) ) out << endl;
        return out.str();
    }
};

int main( int argc, char * args[] )
{
    ifstream fin( "data.csv" );
    if( fin )
    {
        copy( csv_istream_iterator<int>( fin ),
              csv_istream_iterator<int>(),
              formatted_ostream_iterator<int, Formatter>( cout, Formatter() ) );

        fin.close();
    }

    return 0;
}


In this example, I read integers from a comma-delimited text file and use a custom formatter to output them to stdout. The Formatter that I created will output them in a fixed width, hexidecimal, tabular layout.
Last edited on
I got to thinking about my last post, and I think that the formatted_ostream_iterator is useless.

Consider:
1
2
3
4
transform( csv_istream_iterator<int>( fin ),
           csv_istream_iterator<int>(),
           ostream_iterator<string>( cout ),
           Formatter() );


The formatter unary function object performs the conversion from an int to a string and allows the formatting to be performed.
Last edited on
Here is a refinement of the Formatter unary function object that accepts a user-specified delimiter that defaults to a comma. Formatter could be used to write to a ofstream to create CSV files from a data source, such as an istream or a container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

class Formatter
{
    enum { WIDTH = 4, COLUMNS = 8 };
    
    string _delim;

    long _count;
public:
    Formatter( string delim = "," ): _delim( delim ), _count( 0 ) {}

    template<class T>
    string operator ()( const T & t )
    {
        ostringstream out;
        if( _count ) {
            out << _delim;
            if( !( _count % COLUMNS ) ) out << endl;
        }
        out << hex << setw( WIDTH ) << t;
        ++_count;
        return out.str();
    }
};


Example usage:
1
2
3
4
5
6
7
8
9
10
11
12
13
// from CSV file to stdout
transform( csv_istream_iterator<int>( fin ),
           csv_istream_iterator<int>(),
           ostream_iterator<string>( cout ),
           Formatter() );

// from a vector to stdout
vector<int> v;
//...
transform( v.begin(),
           v.end(),
           ostream_iterator<string>( cout ),
           Formatter() );
Topic archived. No new replies allowed.