为什么函数调用不是左值

Why isn't function call an lvalue

这应该很明显,但我无法在标准中明确指出函数调用是(不是)左值的任何规范引用。有一些相关question,但是是关于C++的,没有提供参考。

翻阅 6.5.2.2(p5) Function calls 我唯一能找到的是

If the expression that denotes the called function has type pointer to function returning an object type, the function call expression has the same type as that object type, and has the value determined as specified in 6.8.6.4

6.3.2.1(p1) 表示

An lvalue is an expression (with an object type other thanvoid) that potentiallydesignates an object

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

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

EXAMPLE 1 If f is a function returning a structure or union, and x is a member of that structure or union, f().x is a valid postfix expression but is not an lvalue.

根据这个例子判断,如果 f() 是一个左值 f().x 也是一个左值。但是示例提供的信息让我感到困惑。

它不是左值,因为它在您引用的段落中被描述为 "value"。该标准明确提及表达式何时具有作为左值的 属性 。例如:

6.5.3.2 Address and indirection operators (emphasis mine)

4 The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ''pointer to type'', the result has type ''type''. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

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

6.5.2.3 Structure and union members (emphasis mine)

3 A postfix expression followed by the . operator and an identifier designates a member of a structure or union object. The value is that of the named member, and is an lvalue if the first expression is an lvalue. If the first expression has qualified type, the result has the so-qualified version of the type of the designated member.

因此在您引用的示例中,f().x 是一个值,而不是左值,因为 f() 本身不是左值。

函数的 return 值不是标准定义的左值,但在某些上下文中它会提供左值的语义。

给定任何结构类型:

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

可以编写一个函数,其 return 值可以以需要类型 struct foo 的左值的方式使用 [最典型的是将这种左值的地址传递给另一个函数]。 =19=]

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) 的 return 值不是左值,但 wrap_foo(x).it[0] 是一,并且可以获取其地址。由此确定的对象的生命周期将通过封闭表达​​式的评估来延长,即对 do_something 的调用。如果下标运算符本身被定义为一个运算符,它不会导致数组到指针的分解,而只是产生一个元素类型的值,只有当数组是一个时它才是左值,那么 wrap_foo(x).it[0] 不会是左值,生命周期问题将无关紧要。

虽然传递临时地址的能力很有用,但它要求编译器为 wrap_foo 的 [=29= 分配 space,从而增加了编译器的复杂性] 在将任何参数堆叠到外部函数调用之前的值。如果需要这样的编译器复杂性,它也可以通过允许顶级参数表达式对任意类型的值使用 & 来实现这样的语义(产生一个 const 限定的指针,指向一个生命周期为是外部封闭表达式的那个)。