3

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?

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    What is an `istream_initializer`? I think your best bet is to make the extraction operator start with `*this = Foo{};` to make sure everything is zero initialized. – Martin Bonner supports Monica Jun 07 '16 at 14:36
  • @MartinBonner the extractor knows no `*this`. Did you mean `rhs`? – Captain Giraffe Jun 07 '16 at 14:38
  • 1
    Just add a default constructor that zero-initializes. – Cheers and hth. - Alf Jun 07 '16 at 14:39
  • I beg your pardon. Quite right. It should be `rhs = Foo{};` – Martin Bonner supports Monica Jun 07 '16 at 14:39
  • @MartinBonner Ugh an `istream_initializer` is a typo. Thanks I've edited. I believe you are correct on the extraction operator, I consider it a lesson learned. But more to the question, is there a way to make an `istream_iterator` do the zero-initialization for me? I take it the answer is no? – Jonathan Mee Jun 07 '16 at 14:45
  • Sorry about the answer, I deleted it: the iterator just reuses a single instance of e.g. `Fooz`. :( – Cheers and hth. - Alf Jun 07 '16 at 14:49
  • But you can just do this in your `operator>>`: `rhs = {}`. – Cheers and hth. - Alf Jun 07 '16 at 14:50
  • @Cheersandhth.-Alf Yeah, I hadn't made it to trying out the answer. My guess is that an `istream_iterator` has a member object of the type it is templatized to and streams into that type, so you always get the values left over from the previous read, but it's at least minimally concerning that the `istream_iterator` doesn't even zero-initialize on construction, not withstanding the lack of a zero-initialized reset between extractions. – Jonathan Mee Jun 07 '16 at 14:52
  • 1
    It just doesn't do anything you didn't order. That's reliable behavior. From this simplicity more complex behaviors can be defined, but the opposite, reducing complexity to simplicity, is difficult. – Cheers and hth. - Alf Jun 07 '16 at 14:53

2 Answers2

3

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
1

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. I.e you need to initialize it in some way yourself. I think in the operator>> is a reasonable place.

Captain Giraffe
  • 14,407
  • 6
  • 39
  • 67