Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
537 views
in Technique[技术] by (71.8m points)

c++ - Weird evaluation order in chained functions

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 ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...