2

This should be pretty obvious, but I could not any normative reference in the Standard explicitly that function call is (not) an lvalue. There is somewhat related question, but it's about C++ and no references is provided.

Looking through 6.5.2.2(p5) Function calls the only I could find is

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) states that

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

So I tried to find if a function call designates an object. It is not specified in the Standard if the function call result has storage duration and lifetime. Since any object has storage duration and lifetime I concluded that any function call expression does not designate an object an hence not an lvalue.

But this seems confusing and complicated. In particular I found an example 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.

Judging by this example if f() would be an lvalue f().x would also be an lvalue. But examples are informative which made me confused.

Some Name
  • 8,555
  • 5
  • 27
  • 77

2 Answers2

2

It's not an lvalue because its described as a "value" in the paragraph you quoted. The standard explicitly mentions when an expression has the property of being an lvalue. For instance:

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.

As for accessing a union or member. The standard doesn't require the postfix expression in expr.id to be an lvalue. On the contrary. The whole member access has the same value category as the postfix expression:

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.

So in the example you quoted, f().x is a value, and not an lvalue, because f() itself is not an lvalue.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • The thing that is confusing that the Standard also sometimes mentions explicitly that some expression is _not_ an lvalue. `6.5.3.2(p4)`: _If the operand is the result of a unary `*` operator, neither that operator nor the `&` operator is evaluated and the result is as if both were omitted, that the constraints on the operators still apply and the result is not an lvalue_. e.g `&*E`. So I expceted that there a being an lvalue property would be mentioned somehewhere. – Some Name Apr 07 '19 at 10:01
  • 1
    @SomeName - Expressions in C can only ever be values or lvalues. So I don't see it as a wording defect. It's not an lvalue? Fine, it can only be one other thing. – StoryTeller - Unslander Monica Apr 07 '19 at 10:02
  • 1
    I agree. I incorrectly understood the definition of the term expression, specified in the `6.5(p1)` where it was mentioned explicitly. _An expression is a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof_ – Some Name Apr 07 '19 at 10:07
  • There is a weird quirk in this, though, which stems from the Standard's having defined the effect of using `[]` with an array in terms of array decay and dereferencing, even when the array isn't part of an addressable lvalue. Given `struct foo{struct bar x[1];} someFunc(void);`, the expression `someFunc().x[0]` will be an lvalue. IMHO, the Standard would have been cleaner, and easier to implement, if it specified that use of the the `[]` operator on an array-type value may, at an implementation's option, yield a value of the result type which would be an lvalue only if the array was. – supercat Apr 08 '19 at 15:56
2

The return value of a function is not an lvalue as the Standard defines the term, but there are contexts where it would offer the semantics of one.

Given any structure type:

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

one can write a function whose return value can be used in ways that would require an lvalue of type struct foo [most typically passing the address of such an lvalue to another function].

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

Note that while the return value of wrap_foo(x) isn't an lvalue, wrap_foo(x).it[0] is one, and its address can be taken. The lifetime of the object identified thereby will extend through the evaluation of the enclosing expression, i.e. the call to do_something. If the subscripting operator were defined in its own right as an operator which does not result in array-to-pointer decomposition but simply yields a value of the element type, which would be an lvalue only when the array was one, then wrap_foo(x).it[0] would not be an lvalue, and issues of lifetime would be irrelevant.

While the ability to pass the address of a temporary is useful, it adds compiler complexity by requiring that a compiler given something like the above allocate space for wrap_foo's return value before stacking any of the arguments to the outer function call. If such compiler complexity is required, it could just as well allow such semantics to be achieved by allowing top-level argument expressions to use & on values of arbitrary type (yielding a const-qualified pointer to an object whose lifetime would be that of the outer enclosing expression).

supercat
  • 77,689
  • 9
  • 166
  • 211