Chaining operation and order of evaluation on the same object

橙三吉。 提交于 2020-01-02 13:58:24

问题


Consider a class MyClass with:

  • a member function myClass& myFunction1(int) that modifies the object and returns *this
  • a member function int myFunction2() const that does not modify the object

Does the C++11/14 standard guarantee that:

myclass.myFunction1(myclass.myFunction2()).myFunction1(myclass.myFunction2());

is equivalent to:

myclass.myFunction1(myclass.myFunction2());
myclass.myFunction1(myclass.myFunction2());

回答1:


No. The compiler can first call myclass.myFunction2() twice and then do the two myFunction1 calls in the first example code. But not in the second example code.

There is nothing that stops the compiler from sticking something in between the evaluation of function arguments and the call of the function. As long as the call actually happen after the evaluation of the call arguments (of that function). In Standardese terms

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function.

Different expressions generally are unsequenced (unless there is explicit wording that sequences them). Your two calls to myclass.myFunction2 are such unsequenced cases, so that one of the calls to myclass.myFunction2 can appear after the other (and before the call to any of the myFunction1).




回答2:


Order of evaluation of the operands of any C++ operator, including the order of evaluation of function arguments in a function-call expression, and the order of evaluation of the subexpressions within any expression is unspecified (except where noted below). The compiler will evaluate them in any order, and may choose another order when the same expression is evaluated again. There is no concept of left-to-right or right-to-left evaluation in C++, which is not to be confused with left-to-right and right-to-left associativity of operators: the expression f1() + f2() + f3() is parsed as (f1() + f2()) + f3() due to left-to-right associativity of operator+, but the function call to f3 may be evaluated first, last, or between f1() or f2() at run time.

as stated here for anybody that tried to write the expression in Polish notation and apply c++ operator precedence rules




回答3:


As noted by others, the calls to myFunction2() are unordered relative to each other. So it isn't the same.

As an attempt to make them ordered, we might do something like this:

template<typename T>
struct continuation {
  T&& t;
  continuation(T&& t_):t(std::forward<T>(t_)) {}
  continuation( continuation&& ) = delete;
  continuation( continuation const& ) = delete;
  continuation() = delete;
  void operator=(continuation const&) = delete;
  void operator=(continuation &&) = delete;
  template<typename Lambda>
  continuation<T> then( Lambda&& closure ) && {
    std::forward<Lambda>(closure)( std::forward<T>(t) );
    return { std::forward<T>(t) }; // two forwards!  Dangerous
  }
};
struct myClass {
  continuation<myClass&> myFunction1(int) &{ return {*this}; }
  continuation<myClass> myFunction1(int) &&{ return {*this}; }
  int myFunction2() const { return 7; }
};
int main() {
  myClass c;
  c.myFunction1( c.myFunction2() ).then( [](auto&& c) { c.myFunction1( c.myFunction2() ); } );
}

would be an example of code that would let you chain your use of the class instance on one line with the same order of operations (in C++1y).

However, continuation style then calls are traditionally used for future return values rather than ordered evaluation of chain calls to this.

It also gets really annoying with all of the nested brackets. One way to approach that problem is to use a named operator *then*, which requires a bit more boilerplate, but gives you:

int main() {
  myClass c;
  c.myFunction1( c.myFunction2() )
  *then* [](auto&& c) { c.myFunction1( c.myFunction2() ); }
  *then* [](auto&& c) { c.myFunction1( c.myFunction2() ); };
}

which looks sort of pretty.

All of this is basically a way to avoid creating a simple alias.

myClass get_my_class() { return {} };
int main() {
  auto&& c = get_my_class();
  c.myFunction1(c.myFunction2());
  c.myFunction1(c.myFunction2());
  c.myFunction1(c.myFunction2());
}

both does not require any of the gymnastics above, and is at least as efficient. If you have a complex name for some object and you want to avoid saying it again, using a reference works in all but some corner cases.

The corner case in particular is a function that returns a reference to an object whose lifetime ends at the end of the current line. This is generally bad practice: with easy-to-move objects at least, you should instead return a temporary.

However, a situation where this is unavoidable may occur. Rather than method chaining or continuation or whatever, consider creating a simple lambda or function that chains the operations, and call it on the single line that the object exists on. Alternatively, break the line down and store the lifetime of the object via reference extension of its original incarnation.



来源:https://stackoverflow.com/questions/21890302/chaining-operation-and-order-of-evaluation-on-the-same-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!