C++ C++;后缀表达式未定义与未指定行为

C++ C++;后缀表达式未定义与未指定行为,c++,language-lawyer,c++14,undefined-behavior,operator-precedence,C++,Language Lawyer,C++14,Undefined Behavior,Operator Precedence,提前道歉,我知道关于评估顺序的一般话题已经有很多问题了。然而,在研究了它们之后,我想澄清一些我认为不构成任何重复的具体观点。假设我有以下代码: #include <iostream> auto myLambda(int& n) { ++n; return [](int param) { std::cout << "param: " << param << std::endl; }; } int main() {

提前道歉,我知道关于评估顺序的一般话题已经有很多问题了。然而,在研究了它们之后,我想澄清一些我认为不构成任何重复的具体观点。假设我有以下代码:

#include <iostream>

auto myLambda(int& n)
{
    ++n;
    return [](int param) { std::cout << "param: " << param << std::endl; };
}

int main()
{
    int n{0};

    myLambda(n)(n);
    return 0;
}
#包括
自动myLambda(内部和外部)
{
++n;

return[](int param){std::cout我没有找到对标准的正确引用,但我发现它的行为与所请求的参数求值顺序类似,并且函数参数求值的顺序不由标准定义:

5.2.2函数调用

8[注:后缀表达式和参数表达式的求值都是相对不排序的。参数表达式求值的所有副作用都是在函数之前排序的 输入(见1.9)。-结束注释]

下面是它在不同编译器上调用的过程:

#include <iostream>
#include <functional>

struct Int
{
    Int() { std::cout << "Int(): " << v << std::endl; }
    Int(const Int& o) { v = o.v; std::cout << "Int(const Int&): " << v << std::endl; }
    Int(int o) { v = o; std::cout << "Int(int): " << v << std::endl; }
    ~Int() { std::cout << "~Int(): " << v << std::endl; }
    Int& operator=(const Int& o) { v = o.v; std::cout << "operator= " << v << std::endl; return *this; }

    int v;
};

namespace std
{
    template<>
    Int&& forward<Int>(Int& a) noexcept
    {
        std::cout << "Int&: " << a.v << std::endl;
        return static_cast<Int&&>(a);
    }

    template<>
    Int&& forward<Int>(Int&& a) noexcept
    {
        std::cout << "Int&&: " << a.v << std::endl;
        return static_cast<Int&&>(a);
    }
}

std::function<void(Int)> myLambda(Int& n)
{
    std::cout << "++n: " << n.v << std::endl;
    ++n.v;
    return [&](Int m) { 
        std::cout << "n: " << m.v << std::endl;
    };
}

int main()
{
    Int n(0);

    myLambda(n)(n);
    return 0;
}
MSVC和GCC:

2
1
-1
-二,

叮当声:

1
2
-2
-一,


正如clang order是向前的一样,lambda的参数是在函数的参数求值后传递给lambda的

lambda定义中的
n
是lambda定义的函数的正式参数。它与
myLambda
的参数(也称为
n
)没有关联。这里的行为是完全由调用这两个函数的方式决定。在
myLambda(n)(n)
中,未指定两个函数参数的求值顺序。lambda可以使用0或1参数调用,具体取决于编译器


如果lambda是用
[=n]()…
定义的,则其行为会有所不同。

子表达式的求值顺序未指定,除了运算符&、| |、?和“,”之外,可能会有所不同

编译器既知道函数原型myLambda,也知道返回lambda(从返回类型中提取)。因为我的第一句话编译器是自由的,他首先计算哪个表达式。这就是为什么你永远不应该在表达式中调用具有附加副作用的函数。

具有讽刺意味(由于示例使用了C++11特性,其他答案也因此而分心)使此示例具有未指定行为的逻辑可以追溯到C++98第5节第4段

除非另有说明,否则未指定单个运算符的操作数和单个表达式的子表达式的求值顺序以及副作用发生的顺序。在上一个序列点和下一个序列点之间,标量对象的存储值应通过表达式求值最多修改一次。进一步re,只有在确定要存储的值时才能访问先前值。对于完整表达式的子表达式的每个允许顺序,都应满足本段的要求;否则,行为是未定义的

基本上在所有C++标准中都存在相同的子句,尽管正如Marc van Leeuwen的评论所指出的,最近的C++标准不再使用序列点的概念。净效应是相同的:在语句中,操作符的操作数和单个表达式的子表达式的顺序或评价仍然未指定。 发生未指定行为是因为表达式

n
在语句中计算了两次

myLambda(n)(n);
表达式
n
(用于获取参考)的一个评估与第一个
(n)
相关联,表达式
n
的另一个评估(用于获取值)与第二个
(n)
相关联。这两个表达式的评估顺序(即使它们在光学上都是
n
)没有具体说明

所有C++标准中都存在类似的子句,并且在语句<代码> MyLAMBDA(n)(n)< /> >中有相同的结果-无论<代码> MyLabbDa()< /C> >如何实现< /P> <> >例如,代码> MyLabbDa()/Cype >可以在C++ 98中实现(以及所有后来的C++标准,包括C++ 11和以后的),如

 class functor
 {
      functor() {};
      int operator()(int n) { std::cout << "n: " << n << std::endl; };
 };

 functor myLambda(int &n) 
 {
       ++n;
       return functor();
 }

 int main()
 {
      int n = 0;

      myLambda(n)(n);
      return 0;
 }
类函子
{
函子(){};

int运算符()(int n){std::coutṪ这种行为是未指定的,因为在函数调用
(myLambda(n))(n)
中,后缀表达式(我给了一对多余的括号)相对于参数表达式
n
(最右边的一个)是不排序的。但是,没有未定义的行为,因为对
n
的修改发生在函数调用
myLambda(n)
中。唯一确定的顺序关系是,两者都在计算
myLambda(n)
和final
n
显然是在lambda的实际调用之前排序的。对于最后一个问题,lambda是否选择完全忽略其参数,那么行为不再是未指定的:虽然未指定作为参数传递的值是什么,但两种方式都没有明显的差异。

什么编译器你用过吗?因为结果是
n:1
@teivaz-g++-5 ubuntu版本(gcc版本5.3.0).考虑到question@teivaz如果您选择,结果是
n:0
。这里没有后缀表达式。有趣的是,这可能会在C++17中完全指定,并带有建议。在该建议下,我相信程序必须打印
param:1
,因为函数调用obt在对lambda进行辩论之前,必须对lambda进行评估。我给出了这个答案,结果被否决了(不过,我认为这是完全正确的。我从未暗示lambda打印的
n
与传递到
myLamdba
n
相同。问题不在于此。另外,在
 class functor
 {
      functor() {};
      int operator()(int n) { std::cout << "n: " << n << std::endl; };
 };

 functor myLambda(int &n) 
 {
       ++n;
       return functor();
 }

 int main()
 {
      int n = 0;

      myLambda(n)(n);
      return 0;
 }