31

I was going through the topic of associativity of C operators.

There I came across this fact that the function call operator () has a left to right associativity. But associativity only comes to play when multiple operators of the same precedence occur in an expression. But I couldn't find any example involving function call operator where associativity plays a crucial role.

For example in the statement a = f(x) + g(x);, the result depends on the evaluation order and not on the associativity of the two function calls. Similarly the call f(g(x)) will evaluate function g() first and then the function f(). Here we have a nested function call and again associativity doesn't play any role.

The other C operators in this priority group are array subscript [], postfix ++ and postfix --. But I couldn't find any examples involving a combination of these operators with () where associativity plays a part in expression evaluation.

So my question is does the associativity of function call being defined as left to right affect any expression in C? Can anyone provide an example where the associativity of function call operator () does matter in expression evaluation?

4
  • What if f is an expression itself (like one function pointer selected from an array)?
    – user3185968
    Commented Aug 10, 2015 at 11:07
  • 2
    Any function that returns a pointer to a function can be used as an example, because function pointer dereferencing is not necessary with the function call operator. So you start having things like f()()()() and it's valid C. See Grzegorz's answer for a possible example. Commented Aug 10, 2015 at 11:13
  • 1
    28 upvotes.... It kind of has to be left to right. Would you want to live in a world where f(a)(b) was a call to f passing b then calling the result with a (that is to say (f(b))(a)) - would you?
    – Alec Teal
    Commented Aug 10, 2015 at 23:45
  • I disagree with the existing answers, magnificently voted or not. They do explain some basics well, but this answer goes way deeper. And yes, we could have a long argument about C grammar distinguishing "primary" and "unary" operators, but that distinction is just an artifact of the grammar as well. Commented Aug 11, 2015 at 6:10

3 Answers 3

45

Here is an example, where left-right associativity of function call operator matters:

#include <stdio.h>

void foo(void)
{
    puts("foo");
}

void (*bar(void))(void) // bar is a function that returns a pointer to a function
{
    puts("bar");
    return foo;
}

int main(void)
{
    bar()();

    return 0;
}

The function call:

bar()();

is equivalent to:

(bar())();
9
  • 1
    Brilliant. Thank you so much.
    – Deepu
    Commented Aug 10, 2015 at 11:14
  • 1
    In what sense is associativity relevant when dealing with unary operators that are all prefix or all postfix? I would think it would be mainly relevant in cases where prefix and postfix operators are both applied to the same item, as in *foo(). I can't think of any definition for foo that would allow both (*foo)() and *(foo()) to be legal in C (such a thing could exist in C++), but the fact that each form is legal in some situations and only the latter can be replaced with *foo() would demonstrate the relative associativity of * and ().
    – supercat
    Commented Aug 10, 2015 at 21:33
  • 1
    @GrzegorzSzpetkowski: To my mind, if they were really operators, it would be possible to write something like foo -> (expr ? bar: boz) and have its meaning be equivalent to expr ? foo->bar : foo->boz (actually, come to think of it having C allow that could make some kinds of code more legible, and it wouldn't make any currently-legal constructs ambiguous; it would, however, grossly violate the "no new invention" rule). As it is, though, I think it's more useful to say that binary operators connect expressions, and thus . and -> aren't binary operators, than to regard those things...
    – supercat
    Commented Aug 11, 2015 at 2:56
  • 1
    @supercat: -> and . are operators but they only have one expression operand, on the left. The RHS is an identifier but not an expression. As such they're effectively postfix unary operators, not binary operators. Commented Aug 11, 2015 at 5:03
  • 2
    This is not an example where the associativity of the function call operator matters. There is just no other way that bar()() could possibly be interpreted than as (bar())(); notably bar(()()) is no option, not just because now the argument of bar is not syntactic, but more fundamentally because parentheses that were added now become the "function call operator" themselves, and cannot be omitted. So right(-to-left) associativity would just not make any sense. Nor would it for other postfix operators, which left-associate; similarly prefix operators are necessarily right-associative. Commented Aug 11, 2015 at 11:36
15

In addition to @GrzegorzSzpetkowski's answer, you can also have the following:

void foo(void) { }

int main(void) {
    void (*p[1])(void);
    p[0] = foo;
    p[0]();
    return 0;
}

This creates an array of function pointers, so you can use the array subscript operator with the function call operator.

2
  • Nice Answer. Thank you.
    – Deepu
    Commented Aug 10, 2015 at 12:00
  • 2
    And if those functions in the arrays would be int* (*p[1])(int) instead, you could have p[0](1)[2];. Take the first function pointer, call it with argument 1, and take the third array element returned. There's almost no bound to the complexity of C expressions
    – MSalters
    Commented Aug 10, 2015 at 15:38
3

The statement that function application associates to the left is entirely redundant in the C language definition. This "associativity" is implied by the fact that a function argument list appears right of the function expression itself and must be enclosed in parentheses. This means that for a given function expression, there can never be any doubt about the extent of its argument list: it extends from the obligatory opening parenthesis after the function expression to the matching closing parenthesis.

The function expression (which often is just an identifier, but may be more complicated) might itself be a (bare) function call, as in f(n)(1,0). (Of course this requires the value returned by f(n) to be something that can then be called with argument list (1,0), for which C has some possibilities and C++ a lot more, but these are semantic considerations that only come to play after parsing, so they should be ignored for the associativity discussion.) This means the syntax rule for function calls is left-recursive (the function part can itself be a function call); this may be formulated by saying "function calls associate to the left", but the fact is obvious. By contrast the right part (argument list) cannot be a (bare) function call because of the required parentheses, so there cannot be right recursion.

For instance consider (a)(b)(c) (where I put in redundant parenthesis in order to suggest symmetry and possible ambiguity). Here in itself (b)(c) might be taken to be the call of b (redundantly parenthesised) with argument c; however such a call cannot be construed to be the argument of a, since that would require additional parentheses as in (a)((b)(c)). So without any mention of associativity, it is clear that (a)(b)(c) can only mean that a is called with argument b, and the resulting value is called with argument c.

Not the answer you're looking for? Browse other questions tagged or ask your own question.