Anycorn
Anycorn

Reputation: 51445

C++ expression evaluation order

i ran into a curious problem regarding evaluation of expressions:

reference operator()(size_type i, size_type j) {
  return by_index(i, j, index)(i, j); // return matrix index reference with changed i, j
}

matrix& by_index(size_type &i, size_type &j, index_vector &index) {
  size_type a = position(i, index); // find position of i using std::upper_bound
  size_type b = position(j, index);
  i -= index[a];
  j -= index[b];
  return matrix_(a,b); // returns matrix reference stored in 2-D array
}

I have thought matrix(i,j) will be evaluated after the call to buy_index, so that i, j will be updated. this appears to be correct, i verified in debugger. however, for some types of matrix, specifically those which have to cast size_type the something else, for example int, the update in by_index is lost. modifying code slightly removes the problem:

reference operator()(size_type i, size_type j) {
  matrix &m = by_index(i, j, index);
  return m(i, j); 
}

do you know why the first operator misbehaves? thanks

prototypes which work and which do not

inline reference operator () (size_t i, size_t j); // ublas, size_type is std::size_t
reference operator () (int i, int j); // other prototype, size_type is int

in debugger backtrace stack looks like this:

Upvotes: 4

Views: 1624

Answers (5)

Aureliano Buendia
Aureliano Buendia

Reputation: 625

As an additional remark, this is the typical case where been concise overrules clarity, something Brian Kernighan strongly advises us to avoid (He wrote an excellent book on these matters, "The Practice of Programming"). The evaluation order is not well defined in such code, what leads to the "side effect" of unpredictable results. The change you have made is the recommended approach to situations like this one.

Upvotes: 0

sth
sth

Reputation: 229583

Finally I found where in the standard this is specified (n1905 draft):

(5.2.2-8) - The order of evaluation of arguments is unspecified. All side effects of argument expression evaluations take effect before the function is entered. The order of evaluation of the postfix expression and the argument expression list is unspecified.

The postfix expression mentioned is the part to the left of (). So in the "outer" function call it is not specified if by_index(i, j, index) or it's arguments (i, j) are evaluated first.

There is a sequence point after a function returns, so when by_index(i, j, index) returns all side effects are complete, but the (i, j) parameters might already have been evaluated (and the values been stored in a register or sth.) before that function even go called.

Upvotes: 2

Hexagon
Hexagon

Reputation: 6961

In my opinion, this boils down to order of evaluation.

The standard says -

(5.4) Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

Which fits the bill exactly. The values of i and j may be evaluated before the call to by_index(), or after it. You can't tell - this is unspecified.

I will add that the form that solves your problem is far more readable in my eyes, and I would have used it regardless of correctness of the first form...

Upvotes: 3

Potatoswatter
Potatoswatter

Reputation: 137780

Passing data through argument lists like that is very, very, unclear. Relying on implicit casts between reference types to different-sized bases is very, very unsafe.

Anyway, if you're calling by_index(size_t &,… with an int argument, you're taking the reference of a temporary. This should be a warning, but maybe you're on an older compiler. Try an explicit cast.

reinterpret_cast< size_t & >( i ) /* modify i before next fn call */

But of course that's not guaranteed to do the right thing. Really, you need to sort out unsigned from signed and not assume sizeof(int) == sizeof(size_t) because often it isn't.

Upvotes: 0

sth
sth

Reputation: 229583

I suspect that casting a reference to a different type breaks strict aliasing rules that your compiler uses to optimize more efficiently. You have two variables/references of different type and the compiler assumes that they don't refer to the same memory (but which they in fact do). The compiler then optimizes the code under that wrong assumption which produces wrong results.

You can try to compile with -fno-strict-aliasing (or equivalent) to disable these optimizations and see if it improves the situation.

Upvotes: 2

Related Questions