Question

I must be obviously missing something very basic.

I have a simple class where I perform an "in-place" operation, and then return a reference to this (which allows me to chain different operations). When I print to an std::ostream, I see an unexpected output. The following code will help me explain.

#include<iostream>

struct Container {

  Container(int x, int y, int z)
      : m_data{x, y, z} { }

  int operator()(size_t index) const {
    return m_data[index];
  }

  Container& shift() {
    int tmp = m_data[0];
    m_data[0] = m_data[1];
    m_data[1] = m_data[2];
    m_data[2] = tmp;
    return *this;
  }

  int m_data[3];
};

std::ostream& operator<<(std::ostream& os, const Container& c) {
  os<<"["<<c(0)<<", "<<c(1)<<", "<<c(2)<<"]";
  return os;  
}


int main() {
  std::cout<<std::endl<<"Behaviour Line-By-Line (works as expected)"<<std::endl;

  Container d(1, 2, 3);

  std::cout<<"Container as built:               "<<d<<std::endl;

  d.shift();  
  std::cout<<"Container after first shift:      "<<d<<std::endl;

  d.shift();
  std::cout<<"Container after second shift:     "<<d<<std::endl;

  std::cout<<std::endl<<"Behaviour On The Same Line (not as expected)"<<std::endl;

  Container c(1, 2, 3);

  std::cout<<"Container as built:               "<<c<<std::endl
           <<"Container after first shift:      "<<c.shift()<<std::endl
           <<"Container after second shift:     "<<c.shift()<<std::endl;


  return 0;
}

Compiling (OS X 10.7.4 using GCC 4.8.1) and running:

$ g++ example.cpp -std=c++11 -Wall -Wextra
$ ./a.out

Behaviour Line-By-Line (works as expected)
Container as built:               [1, 2, 3]
Container after first shift:      [2, 3, 1]
Container after second shift:     [3, 1, 2]

Behaviour On The Same Line (not as expected)
Container as built:               [3, 1, 2]
Container after first shift:      [3, 1, 2]
Container after second shift:     [3, 1, 2]

As you can see, the output when I place the modifying operation on the same line as the operator<< seems to buffer (for lack of a better word) the change.

My question is:

Why does this happen, and how can I make the "On The Same Line" behavior match the "Line-By-Line behavior.

Thanks!

Edit:

As per @KeithSmith's recommendation, I modified Container::shift to:

  Container& shift() {
    std::cout<<"shifting... "<<std::flush;
    int tmp = m_data[0];
    m_data[0] = m_data[1];
    m_data[1] = m_data[2];
    m_data[2] = tmp;
    return *this;
  }

and got the output:

Behaviour Line-By-Line (works as expected)
Container as built:               [1, 2, 3]
shifting... Container after first shift:      [2, 3, 1]
shifting... Container after second shift:     [3, 1, 2]

Behaviour On The Same Line (not as expected)
shifting... shifting... Container as built:               [3, 1, 2]
Container after first shift:      [3, 1, 2]
Container after second shift:     [3, 1, 2]

As explained in several answers, the order of the operations is not defined. In the "not as expected" case the shifting occurs before the streaming, but I guess it could happen in any order.

My take-away: be very, very careful when inlining operations with side effects! I guess I should have known this! Sadly I did not!

Was it helpful?

Solution 2

The second cout translates roughly to operator <<(operator <<(operator <<(cout, c), c.shift(), c.shift()) and since the argument evaluation order is unspecified, all the shifts can happen in the beginning.

The ; in the first cout introduce sequence points, which ensure the evaluation order. See e.g. this: Undefined behavior and sequence points for more info on sequence points.

OTHER TIPS

The order in which function arguments are evaluated is not specified. If you expressions have side effects and you depend on their order, you need to make sure you execute things in order. That is, the statements

std::cout<<"Container as built:               "<<c<<std::endl
         <<"Container after first shift:      "<<c.shift()<<std::endl
         <<"Container after second shift:     "<<c.shift()<<std::endl;

can be processed as

  • first evaluate one of the c.shift()
  • second evaluate the other c.shift()
  • now it has all objects evaluated and starts outputting them.

It can as well evaluate expressions in a different order. Just the function calls of the shift operators is ordered and, obviously, where it depends on a subexpression the subsexpression needs to be evaluate first.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top