Weird evaluation order in chained functions

筅森魡賤 提交于 2020-03-16 06:07:10

问题


I have this code, which is used to build a graphic interface on a LCD display on a controller; the code is compiled for 2 different architectures using both AVR and PIC32:

    FishinoTftGuiLabel *l1;
    FishinoTftGui
        .Page(F("Page1"))
            .Label(50, 140, 0, 24, LabelAlign::Left, F("Slider value:"))
                .getElement(l1)
                --
            .Label(l1->x() + l1->w() + 10, 140, 0, 24, LabelAlign::Left, F("pippo"))
    ;

Each member function return the same object (or a related one); so for example the Label() function returns the FishinoTftGuiLabel reference, which can be used for chaining other calls. The getElement(T *&) is simply a way to get a pointer to current object, which can be used in following calls without breaking the chain and having to use intermediate variables for each object; the -- operator returns back to the containing Page object. My problem is that the 'l1' pointer, which should be set by getElement on first Label creation, is set AFTER the whole stuff terminates, but just on AVR platform; on PIC32 the evaluation is ok.

So, on PIC32 the order is following:
1) the first Label statement is evaluated and the label is created
2) the getElement(l1) is executed, storing the reference of first label
3) the second Label statement is evaluated; the l1->x() are correctly using the Label1 reference

On the AVR platform, this happens:
1) All arguments of ALL Label() calls get evaluated at first, so the l1->x() crashes, because of calling an uninitialized object's member
2) The Label() functions are evaluated next

My question : is it a compiler bug, or the evaluation order between chained calls is not guaranteed in this case ? Is there a way to force the right evaluation order, without having to break the whole stuff in multiple statements ?


回答1:


As mentioned in the comment, until C++17, the order of evaluation of unsequenced subexpressions in function arguments is unspecified, so it's not a compiler bug: both orderings are allowed (even resulting in undefined behaviour if those expressions result in more than one read/write to the same scalar variable, eg as in f(i++,i++)).

Since C++17, postfix expressions (like function calls) evaluate left-to-right; the evaluation order of function arguments is still unspecified, but cannot interleave. So your code will always give the wanted result since C++17.

As a workaround, you may let Label and friends also accept lambdas as parameters, to support lazy(and hence ordered) evaluation, something like:

template<typename T>
auto constref_or_evaluate(T const& t) -> T const&
    { return t; }

template<typename T>
auto constref_or_evaluate(T&& t) -> decltype(std::forward<T>(t)())
    { return std::forward<T>(t)(); }

// the type of FishinoTftGui
struct FishinoTftGuiType
{
  // chainable members ...

  template<typename... T>
  auto Label(T&&... t) -> FishinoTftGuiType&
  {
    LabelImpl( constref_or_evaluate(std::forward<T>(t))... );

    return *this;
  }

private:
  // the original chainable member implementations ...

  void LabelImpl(int,int); //whatever
};

// to be used as

FishinoTftGui
  .Label(1,2)
  .Label([]{return 3;},4);

here the lambda in the second Label() will be always invoked after the first Label() had been fully evaluated.

This has also the advantage of giving finer control on when a lazy expression is evaluated (say, the label could update the lazy parameter whenever the view is resized, etc...). So, it might be worth considering in >=C++17 code as well.

As far as I can tell, this is just C++11; anyway, if you also want to pass l/rvalue reference parameters you'll need to write a forward_or_evaluate() function; this is perfectly doable in C++11, but it's a bit harder to implement.



来源:https://stackoverflow.com/questions/48476469/weird-evaluation-order-in-chained-functions

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