简体   繁体   中英

istream_iterator Does Not Zero-Initialize

This is an Minimal, Complete, Verifiable Example I understand that this is not copacetic. Anyway, given the struct:

struct Foo {
    int even;
    int odd;
};

istream& operator>>(istream& lhs, Foo& rhs) {
    int input;

    lhs >> input;

    (input % 2 == 0 ? rhs.even : rhs.odd) = input;

    return lhs;
}

I can do the following:

stringstream bar("1 2 3 4 5 6 7 8 9 0");

for (const auto& i : vector<Foo>{istream_iterator<Foo>(bar), istream_iterator<Foo>()}) {
    cout << i.even << ' ' << i.odd << endl;
}

However this gives me the results:

-1215720516 1
2 1
2 3
4 3
4 5
6 5
6 7
8 7
8 9
0 9

To zero-initialize the Foo I can write the code:

for(Foo i{}; bar >> i; i = Foo{}) {
    cout << i.even << ' ' << i.odd << endl;
}

This gives m my expected result:

0 1
2 0
0 3
4 0
0 5
6 0
0 7
8 0
0 9
0 0

I understand that having an extraction operator that does not fully overwrite the variable is sketchy. This is initially stemming from my answer here and my question here which in my mind had a more natural expectation of zero-initializing the variable in-between reads. In any case, is it possible to use an istream_iterator such that the variable is zero-initialized between reads, or must I use the for -loop?

in my mind had a more natural expectation of zero-initializing the variable in-between reads

That is an incorrect expectation. operator>> should completely and solely responsible for initializing the object. You cannot assume that the object will have been previously default/value-initialized. A pretty standard use-case is reading in all the objects in a while loop:

Foo foo;
while (std::cin >> foo) { ... }

The 2nd time through, foo will have whatever the old values were - there is no zeroing anywhere here. So you need to make sure that when your operator returns, the new object is completely set by you.

The simplest thing is to value-initialize it first:

istream& operator>>(istream& lhs, Foo& rhs) {
    int input;    
    lhs >> input;
    rhs = Foo{}; // <== add this
    input % 2 == 0 ? rhs.even : rhs.odd) = input;
    return lhs;
}

Alternatively you could manually write both:

if (input % 2 == 0) {
    rhs.odd = 0;
    rhs.even = input;
}
else {
    rhs.odd = input;
    rhs.even = 0;
}

or aggregate-initialize each case:

rhs = input % 2 == 0 ? Foo{input, 0} : Foo{0, input};

Regardless, operator>> is responsible for zeroing out values that you want zeroed out.

Is it possible to use an istream_iterator such that the variable is zero-initialized between reads?

If you look into the internals of the istream_iterator it has the following internal state.

private:
  istream_type* _M_stream;
  _Tp       _M_value;
  bool      _M_ok;

Where _M_value is default constructed.

When it++/++it is used it calls _M_read() which has the following pertinent line in its implementation.

*_M_stream >> _M_value;

So the iterator itself never touches the state of your Foo outside of your extrator and _M_value is reused between calls. Ie you need to initialize it in some way yourself. I think in the operator>> is a reasonable place.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM