链接函数中的奇怪评估顺序 [英] Weird evaluation order in chained functions

查看:86
本文介绍了链接函数中的奇怪评估顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这段代码,用于在控制器的LCD显示屏上建立图形界面;该代码使用AVR和PIC32为2种不同的体系结构进行了编译:

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"))
    ;

每个成员函数都返回相同的对象(或相关的对象);因此,例如Label()函数返回FishinoTftGuiLabel引用,该引用可用于链接其他调用. getElement(T *&)只是获取指向当前对象的指针的一种方法,可以在后续调用中使用它,而不必中断链,并且不必为每个对象使用中间变量; -运算符返回到包含的Page对象. 我的问题是,"l1"指针应在整个标签终止后设置,然后由第一个Label创建时的getElement设置,但只能在AVR平台上设置;在PIC32上评估还可以.

因此,在PIC32上的顺序如下:
1)评估第一个Label语句并创建标签
2)执行getElement(l1),存储第一个标签的引用
3)计算第二个Label语句; l1-> x()正确使用Label1引用

在AVR平台上,会发生以下情况:
1)首先对ALL Label()调用的所有参数求值,因此l1-> x()崩溃,因为调用了未初始化对象的成员
2)接下来评估Label()函数

我的问题:是编译器错误,还是在这种情况下不能保证链接调用之间的评估顺序?有没有一种方法可以强制正确的评估顺序,而不必在多个语句中破坏整个内容?

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 ?

推荐答案

如注释中所述,直到C ++ 17之前,函数参数中未排序的子表达式的求值顺序均未指定,因此它不是编译器错误:两者都允许进行排序(如果这些表达式导致对同一个标量变量进行多次读/写操作,甚至会导致未定义的行为,例如f(i++,i++)).

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++)).

自C ++ 17起,后缀表达式(如函数调用)从左到右求值;函数参数的求值顺序仍未指定,但不能交织.因此,自C ++ 17起,您的代码将始终提供所需的结果.

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.

作为一种解决方法,您可以让Label和朋友也接受lambda作为参数,以支持惰性(因此有序)求值,例如:

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);

在这里,第二个Label()中的lambda将始终在第一个Label()被完全评估之后被调用.

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

这还有一个优势,可以更好地控制何时计算惰性表达式(例如,只要调整视图大小,标签就可以更新惰性参数,等等.).因此,也可能值得考虑使用> = C ++ 17代码.

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.

据我所知,这只是C ++ 11;无论如何,如果您还想传递l/rvalue参考参数,则需要编写一个forward_or_evaluate()函数.这在C ++ 11中是完全可行的,但实现起来有点困难.

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.

这篇关于链接函数中的奇怪评估顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆