为什么函数调用不是左值

问题描述 投票:1回答:2

这应该是非常明显的,但我无法在标准中明确指出函数调用是(非)左值。有一些相关的question,但它是关于C ++并没有提供参考。

透过6.5.2.2(p5) Function calls,我唯一能找到的是

如果表示被调用函数的表达式具有返回对象类型的函数的类型指针,则函数调用表达式与该对象类型具有相同的类型,并且具有根据6.8.6.4中指定的值确定的值

6.3.2.1(p1)

左值是一个表达式(具有除void之外的对象类型),可以指定一个对象

所以我试图找出函数调用是否指定了一个对象。如果函数调用结果具有存储持续时间和生存期,则未在标准中指定。由于任何对象都有存储持续时间和生命周期,因此我得出结论,任何函数调用表达式都不指定对象,因此不是左值。

但这似乎令人困惑和复杂。特别是我找到了一个例子6.5.2.3(p7)

示例1如果f是返回结构或联合的函数,并且x是该结构或联合的成员,则f().x is是有效的后缀表达式但不是左值。

从这个例子判断,如果f()将是一个左值f().x也将是一个左值。但是一些例子让我感到困惑。

c language-lawyer lvalue function-call
2个回答
1
投票

它不是左值,因为它在你引用的段落中被描述为“值”。当表达式具有左值属性时,标准明确提及。例如:

6.5.3.2地址和间接操作符(强调我的)

4一元*运算符表示间接。如果操作数指向函数,则结果是函数指示符;如果它指向一个对象,则结果是指定该对象的左值。如果操作数的类型为''指向类型'',则结果的类型为''type''。如果为指针分配了无效值,则unary *运算符的行为未定义。

至于访问工会或会员。该标准不要求expr.id中的后缀表达式为左值。反之。整个成员访问具有与后缀表达式相同的值类别:

6.5.2.3结构和工会成员(强调我的)

3后缀表达式后跟.运算符,标识符指定结构或联合对象的成员。该值是指定成员的值,如果第一个表达式是左值,则该值是左值。如果第一个表达式具有限定类型,则结果具有指定成员类型的限定版本。

所以在你引用的例子中,f().x是一个值,而不是左值,因为f()本身不是左值。


2
投票

函数的返回值不是左值,因为标准定义了术语,但是有一些上下文可以提供一个语义。

给定任何结构类型:

struct foo {...whatever... };

可以编写一个函数,其返回值可以使用需要struct foo类型的左值的方式[最通常将这样的左值的地址传递给另一个函数]。

struct wrapped_foo {struct foo it[1];} wrap_foo(foo it)
{
  struct wrapped_foo ret = {it};
  return ret;
}
extern void do_something(int,int,int,struct foo const *p,int,int,int);
void demo_of_passing_address_of_a_foo(struct foo x)
{
  do_something(1,2,3,&(wrap_foo(x).it[0]),4,5,6);
}

请注意,虽然wrap_foo(x)的返回值不是左值,但wrap_foo(x).it[0]是1,并且可以采用其地址。由此确定的对象的寿命将通过对封闭表达式的评估延伸,即对do_something的调用。如果下标运算符本身被定义为一个运算符,它不会导致数组到指针的分解,而只是产生一个元素类型的值,只有当数组为1时才是左值,那么wrap_foo(x).it[0]就不会是一个左倾,一生的问题是无关紧要的。

虽然传递临时地址的能力很有用,但它会增加编译器的复杂性,要​​求编译器给出类似上面的内容,在将任何参数堆叠到外部函数调用之前为wrap_foo的返回值分配空间。如果需要这样的编译器复杂性,它也可以允许通过允许顶级参数表达式在任意类型的值上使用&来实现这样的语义(产生一个const限定指针,该指针指向一个对象的生命周期将是外围表达)。

© www.soinside.com 2019 - 2024. All rights reserved.