Stream iterators code review

If we want to understand the code below which works appropriately:

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
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>

int main()
{
	std::string from, to;

	std::cout << "Please enter name of input file with the extension\n"
		"The file must already exist.\n";

	std::cin >> from;
	std::ifstream is{ from };

	if (!is)
	{
		std::cout << "Could not open file: " << from << "\nprogram terminating" << '\n';
		system("pause");
		return 1;
	}

	std::cout << "\nPlease enter name of output file\n"
		"If the file already exists it will be replaced \n";
	// File name should include its extension too

	std::cin >> to;
	std::ofstream os{ to };

	if (!os)
	{
		std::cout << "Could not open file: " << to << "\nprogram terminating" << '\n';
		system("pause");
		return 1;
	}

	std::istream_iterator<std::string> ii{ is };
	std::istream_iterator<std::string> eos;
	std::ostream_iterator<std::string> oo{ os, "\n" };
	// Saving on the working directory of that project

	std::cout << "\nAttempting to read from file " << from << '\n';

	std::vector<std::string> b{ ii,eos };

	std::cout << "Number of words read = " << b.size() << '\n';

	std::cout << "Sorting...\n";
	sort(b.begin(), b.end());

	std::cout << "\nWriting sorted words to output\n";
	copy(b.begin(), b.end(), oo);

	std::cout << "\nThese are the words after sorting:\n\n";
	for (auto const& b1 : b)
		std::cout << b1 << ' ';

	std::cout << "\n\n";

	system("pause");
	return 0;
}


1) On line 37: std::istream_iterator<std::string> ii{ is }; the type input stream iterator ii is initialised by is which is of type "input file stream"! How do these two types match?

2) On line 39: std::ostream_iterator<std::string> oo{ os, "\n" }; the iterator oo is initialised by two things, on and "\n". I'm not confident I undestand it well.

3) On line 44: std::vector<std::string> b{ ii,eos }; the interator ii is the beginning but for the vector to match its end to nullptr to work properly, eos should be nullptr, whilst it's empty here! I don't understand it either!
Last edited on
You should really know about constructors and their parameters by now.

1/2 See:

http://www.cplusplus.com/reference/iterator/istream_iterator/?kw=istream_iterator
http://www.cplusplus.com/reference/iterator/ostream_iterator/?kw=ostream_iterator

3
eos should be nullptr
Why? eos is the right end iterator here.

Read the reference for the stream iterators and it should be clear.
Constructs an istream iterator that is associated with stream s (the object does not own or copy the stream, only stores a reference).
Constructs an ostream iterator that is associated with stream s (the object does not own or copy the stream, only stores a reference).
If delimiter is specified, and is not a null pointer, it is inserted after every element is inserted in the stream.


3)
1
2
vector (InputIterator first, InputIterator last,
          const allocator_type& alloc = allocator_type());
That last iterator is pointing to one beyond the last element in the vector. If it can not be a nullptr, then how can the loops recognize the end of the vector when dealing with its data?
Last edited on
That last iterator is pointing to one beyond the last element in the vector. If it can not be a nullptr, then how can the loops recognize the end of the vector when dealing with its data?

Remember that iterators are similar to pointers, so if the "loop counter" (an iterator) is equal to end() (another iterator) the loop is finished. The loop is comparing "addresses" not values.


The loop is comparing "addresses" not values.
Yeah, right, good point.
Yes, I've specifically recently been toying with STL containers within my forgoing threads, myVec, mySet etc.

So in those containers we set begin() to point to the first element and end() to one beyond the last one. Both iterators have their own addresses. Therefore when the loop reaches the address assigned to end(), it stopes and ...

As well as, eos has merely been declared not initialized, hence lacks an address!
Last edited on
As well as, eos has merely been declared not initialized, hence lacks an address!

If the program actually compiled, it would have "address", some random address but an address in any case. But since the program doesn't seem to compile I don't really understand what your problem actually is.

The program compiles perfectly on my VS 2019 (using C++ 17).

The question is, on line 44 a vector is created using two iterators, one pointing to the file stream, ii, to be used as begin() for the vector, and the other some iterator conveying some address, eos, to be used for as end() for the vector!!! These begin() and end() have been applied by sort() on line 49! And then the values are printed by them on line 55! :(
The question is, how using an iterator conveying a random address does the vector pinpoint the end of its range!?

end() should specifically point to the element one past the last one, but here eos points to somewhere. Who knows it's the end of the container? God knows what address it points to!


The program compiles perfectly on my VS 2019 (using C++ 17).

Different systems will include different headers from other headers. Your system is including <iterator> and <string> from one or more of the headers you've included. Other systems may not do that since it is not part of the standard. Since you are using std::istream_iterator, etc., you should explicitly include <iterator>. And since you are using std::string you should explicitly include <string>.

God knows what address [eos] points to!

It's doesn't point to a random address. It doesn't point into the vector at all if that's what you're thinking (the vector iterators and istream iterators are different things). The default istream_iterator constructor is called for eos which initializes the iterator object to whatever it needs to "point" to the end of the stream. What exactly that is depends on how it's implemented. One possibility is that when you pass an istream the iterator stores a pointer to that istream object. When you don't pass an istream (therefore calling the default constructor) it just stores a nullptr instead. Then all the iterator needs to do to implement eof detection at the iterator level is to change the istream pointer in the iterator to nullptr when eof is detected. Then it will equal the end-of-stream iterator.

The vector constructor that takes a begin and end iterator (of whatever kind) presumably does something like:

1
2
3
4
5
6
// pseudocode:
vector(iterator begin, iterator end)
{
    for (iterator it = begin; it != end; ++it)
        push_back(*it)
}

And the istream iterator might look something like the following. It needs a little more detail than this to be fully generalized type-wise (it should inherit from std::iterator, for instance) but this is pretty close to the real thing.

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
73
74
75
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>

template <typename T>
class my_istream_iterator
{
    std::istream* is;
    T value;
public:
    // These types are needed for a standard iterator.
    using value_type        = T;
    using difference_type   = std::ptrdiff_t;
    using pointer           = T*;
    using reference         = T&;
    using iterator_category = std::input_iterator_tag;

    my_istream_iterator() : is(nullptr) { } // the end-of-stream iterator
    my_istream_iterator(std::istream& is_in) : is(&is_in)
        { if (!(*is >> value)) is = nullptr; }

    const T& operator*()  const { return value; }
    const T* operator->() const { return &(operator*()); }

    my_istream_iterator& operator++()
    {
        if (!(*is >> value)) is = nullptr;
        return *this;
    }
    my_istream_iterator operator++(int)
        { my_istream_iterator x(*this); ++(*this); return x; }

    friend bool operator==(const my_istream_iterator& a,
                           const my_istream_iterator& b)
       { return a.is == b.is; }
    friend bool operator!=(const my_istream_iterator& a,
                           const my_istream_iterator& b)
       { return !(a == b); }
};

int main()
{
    using std::cout;

    std::string from, to;

    cout << "Enter name of input file: ";
    std::cin >> from;
    std::ifstream is{ from };
    if (!is) { cout << "Cannot open input file\n"; return 1; }

    cout << "Enter name of output file: ";
    std::cin >> to;
    std::ofstream os{ to };
    if (!os) { cout << "Cannot open output file\n"; return 1; }

    //std::istream_iterator<std::string> ii{ is }, eos;
    my_istream_iterator<std::string> ii{ is }, eos;
    std::ostream_iterator<std::string> oo{ os, "\n" };

    cout << "\nAttempting to read from file " << from << '\n';
    std::vector<std::string> b{ ii, eos };
    cout << "Number of words read = " << b.size() << '\n';

    cout << "\nWriting sorted words to output\n";
    std::sort(b.begin(), b.end());
    std::copy(b.begin(), b.end(), oo);

    cout << "\nThese are the words after sorting:\n\n";
    for (auto const& b1 : b) cout << b1 << ' ';
    cout << '\n';
}

Last edited on
Topic archived. No new replies allowed.