Topic: Order of evaluation for operator parameters


Author: swf@elsegundoca.ncr.com (Stan Friesen)
Date: 1996/08/23
Raw View
In article <m3zq3rtteo.fsf@gabi-soft.fr>, kanze@gabi-soft.fr (J. Kanze)
writes:

|> I presume that, for the purposes of standardization, throwing an
|> exception is a side-effect, and follows the same rules as any other side
|> effect.

In effect, yes.  The standard states that in the case of an exception
being thrown, only those values that were stored as of the last sequence
point may be reliably accessed. Conversely, it says that accessing any
value that has changed since the last sequence point prior to the exception
produces an unspecified or undefined value (I forget which).

And of course the reason for this is exactly the indeterminacy of evaluation
ordering.

--
swf@elsegundoca.ncr.com  sarima@ix.netcom.com

The peace of God be with you.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: jodle@bix.com (jodle)
Date: 1996/08/19
Raw View
Herb Sutter (herbs@cntc.com) wrote:

:   operator<<(operator<<(parm1,parm2),parm3);
       [1]       [2]       [3]   [4]    [5]

It isn't that you're missing a rule, you're just not getting the whole
picture.  For example, in the outer call, it doesn't really matter at all
which order [2] and [4] are evaluated in.  The issue is that [2] and [4]
must both be evaluated before [1] is executed.  While [2] is evaluated,
[3] and [4] are evaluated in some convenient order and [2] is then given
control.  The reference returned by [2] is then passed along with [5] to
[1] and we get the calling order we're now familiar with.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: kanze@gabi-soft.fr (J. Kanze)
Date: 1996/08/20
Raw View
herbs@cntc.com (Herb Sutter) writes:

> An interesting question occurred to me today.  It derives from the
> fact that the order of evaluation of function parameters is undefined.
>
> Consider operator<<() (for example), and the statement:
>
>   parm1 << parm2 << parm3;
>
> Clearly the actual function calls to operator<<() must always occur in
> the expected order.  However, in what order are the parameters
> evaluated?  I thought that the answer would depend on whether
> operator<<() is a member function or a global function.
>
> For example, if operator<<() is a member function, then the above
> statement is equivalent to:
>
>   parm1.operator<<(parm2).operator<<(parm3);
>
> In this case, I thought the parms must be evaluated in the order given
> since they weren't parms to the same function call.  I also thought
> that if OBJ.operator<<(parm1) failed (e.g., threw an exception), parm2
> and parm3 would never be evaluated.  My compilers disagree... both
> MSVC4.1 and BC++5.0 evaluate them 3-2-1 before performing the first
> op<< call.

The order of evaluation is undefined.  Whether they are parameters to
the same function or not has nothing to do with it.  All you are
guaranteed is that:

1.  both parm1 and parm2 are fully evalutated (including side-effects)
before the first call to operator<<, and

2.  the first call to operator<< has returned, and parm3 (including
side-effects) has been fully evaluated before the second call to
operator<<.

You are also guaranteed that none of the side effects take place before
the preceding statement has finished.

On the other hand, there is no ordering constraints whatsoever on the
evaluation of the three arguments.  In fact, if the arguments are of the
form "f() + g()" (for example), there is no guarantee that some of the
function calls of different arguments are not interlaced.

I presume that, for the purposes of standardization, throwing an
exception is a side-effect, and follows the same rules as any other side
effect.

In practice, of course, this means that you should generally avoid
complicated expressions which have side effects if you are using
exceptions.  (Many people, including myself, would also say that you
should avoid them if you want readable code:-).)  Unordered side-effects
may make it very difficult to back out correctly.

--
James Kanze           (+33) 88 14 49 00          email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs Bourgeois, 67000 Strasbourg, France
Conseils en informatique industrielle --
                            -- Beratung in industrieller Datenverarbeitung
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: jodle@bix.com (jodle)
Date: 1996/08/21
Raw View
jodle (jodle@bix.com) wrote:
: Herb Sutter (herbs@cntc.com) wrote:

: :   operator<<(operator<<(parm1,parm2),parm3);
:        [1]       [2]       [3]   [4]    [5]

: It isn't that you're missing a rule, you're just not getting the whole
: picture.  For example, in the outer call, it doesn't really matter at all
: which order [2] and [4] are evaluated in.  The issue is that [2] and [4]
                       |                                                |
should be             [5]                                              [5]



[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: herbs@cntc.com (Herb Sutter)
Date: 1996/08/18
Raw View
An interesting question occurred to me today.  It derives from the
fact that the order of evaluation of function parameters is undefined.

Consider operator<<() (for example), and the statement:

  parm1 << parm2 << parm3;

Clearly the actual function calls to operator<<() must always occur in
the expected order.  However, in what order are the parameters
evaluated?  I thought that the answer would depend on whether
operator<<() is a member function or a global function.

For example, if operator<<() is a member function, then the above
statement is equivalent to:

  parm1.operator<<(parm2).operator<<(parm3);

In this case, I thought the parms must be evaluated in the order given
since they weren't parms to the same function call.  I also thought
that if OBJ.operator<<(parm1) failed (e.g., threw an exception), parm2
and parm3 would never be evaluated.  My compilers disagree... both
MSVC4.1 and BC++5.0 evaluate them 3-2-1 before performing the first
op<< call.

However, if operator<<() is a global function, then the original
statement is equivalent instead to:

  operator<<(operator<<(parm1,parm2),parm3);

Here it seems definite that the order of evaluation of the parms is
undefined.  For example, in the outer call, either parm3 or
operator<<(parm1,parm2) could be evaluated first.  It seems to me that
the possible orderings are: 1-2-3, 2-1-3, 3-1-2, and 3-2-1 (i.e., 3
must be either first or last), while 1-3-2 and 2-3-1 are impossible.
Is this correct?


Here's some test code and runtime output:


#include <iostream.h>

class Test
{
public:
    Test(int i) : _i(i) {};
    int _i;
/*
    Test& operator<<( Test& rhs )
    {
      cout << "<<(" << _i << "," << rhs._i << ")" << endl;
      throw 1;
      return *this;
    }; //*/
};

Test f(int i) { cout << i << endl; return i; };

//*
Test& operator<<( Test& lhs, Test& rhs )
{
  cout << "<<(" << lhs._i << "," << rhs._i << ")" << endl;
  throw 1;
  return lhs;
}; //*/

int main( int, char*[] )
{
  Test t = f(1) << f(2) << f(3);  // if global, op<<( op<<(1,2) , 3 )
                                  // if member, 1.op<<(2).op<<(3)
  return 0;
}


The output is the same whether Test::operator<< or
operator<<(Test&,Test&) is commented out:

3
2
1
<<(1,2)


What rule am I missing?  Thanks in advance,

Herb

---
Herb Sutter (herbs@cntc.com)

Current Network Technologies Corp.
3100 Ridgeway, Suite 42, Mississauga ON Canada L5L 5M5
Tel 416-805-9088  Fax 905-608-2611


[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]