keveman
keveman

Reputation: 8487

C syntax for functions returning function pointers

Consider the following typedefs :

typedef int (*f1)(float);
typedef f1 (*f2)(double);
typedef f2 (*f3)(int);

f2 is a function that returns a function pointer. The same with f3, but the type of the function, the pointer to which f3 returns, is f2. How can I define f3 without the typedefs? I know typedefs are the cleaner and easier to understand way to define f3. However, my intention here is to understand C syntax better.

Upvotes: 39

Views: 26456

Answers (7)

rplgn
rplgn

Reputation: 121

Inspired by John Bode's answer, I want to present it in a graphical way. It should help you to understand how the compiler would lex it into an AST:

Function in function return value, recursively

We first (1) start with the packed type f3 as denoted by "typedef f2 (*f3)(int);". It is a function type by itself. Then it is unpacked one step further in (2) which puts the enclosing curly brackets, in essence performing the "is-function" CFG production of the C language (I assume that the reader has a basic idea of programming language lexical analysis). By taking a look at (3) you see that we have recursively performed the "is-function" lexing step three times, the three times being visible by the "function" nodes in the graph.

Trying my hands at the required (but simplified) CFG productions in custom notation, they could look like...

declaration -> underlying_type:specifier sp ( func-ptr-decl | specifier )
func-ptr-decl -> '(' sp '*' sp ( func-ptr-decl | specifier ) sp ')' sp '(' sp param-list sp ')'

specifier being a string of characters that is best explained as variable names in the C programming language, sp being an optional string of whitespace, param-list being simplified as a (possibly empty) comma-separated list of declarations.

In C each statement that introduces a variable or parameter is called a declaration. Declarations consist of a location/name, the type of the data and an initializer. In the given question we have declarations whose types are pointer-to-function. The pointer-to-function type is recursively nested up to three times. The graph shows it in the way how the arrows are pointing at types meaning they are nesting inside of other types.

Upvotes: 2

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136238

Learn the the right-left rule:

The "right-left" rule is a completely regular rule for deciphering C declarations. It can also be useful in creating them.

Upvotes: 6

Inverse
Inverse

Reputation: 4476

Use std::function:

typedef std::function<int(float)> f1;
typedef std::function<f1(double)> f2;
typedef std::function<f2(int)>    f3;

or

typedef std::function<std::function<std::function<int(float)>(double)>(int)> f3;

Upvotes: 1

John Bode
John Bode

Reputation: 123448

Start with your declaration for f1:

int (*f1)(float);

You want f2 to be a pointer to a function returning f1, so substitute f1 in the declaration above with the declaration for f2:

int (*      f1     )(float);
            |
      +-----+-----+
      |           |
      v           v
int (*(*f2)(double))(float);

The declaration reads as

        f2                   -- f2
       *f2                   -- is a pointer
      (*f2)(      )          -- to a function
      (*f2)(double)          --   taking a double parameter
     *(*f2)(double)          --   returning a pointer
    (*(*f2)(double))(     )  --   to a function
    (*(*f2)(double))(float)  --     taking a float parameter
int (*(*f2)(double))(float)  --     returning int

You repeat the process for f3:

int (*(*    f2    )(double))(float);
            |
        +---+----+
        |        |
        v        v
int (*(*(*f3)(int))(double))(float);

which reads as

          f3                           -- f3
         *f3                           -- is a pointer
        (*f3)(   )                     -- to a function
        (*f3)(int)                     --   taking an int parameter
       *(*f3)(int)                     --   returning a pointer
      (*(*f3)(int))(      )            --   to a function
      (*(*f3)(int))(double)            --     taking a double parameter
     *(*(*f3)(int))(double)            --     returning a pointer
    (*(*(*f3)(int))(double))(     )    --     to a function
    (*(*(*f3)(int))(double))(float)    --       taking a float parameter
int (*(*(*f3)(int))(double))(float);   --       returning int

Upvotes: 145

Puppy
Puppy

Reputation: 146910

In C++, the miracle of templates can make this a tad easier.

#include <type_traits>

std::add_pointer<
    std::add_pointer<
        std::add_pointer<
            int(float)
        >::type(double)
    >::type(int)
>::type wow;

Upvotes: 15

Just don't. It can be done, but it will be very confusing. Typedef's are there to ease writing and reading this short of code.

A function f that takes no arguments and returns a function pointer int (*)(float) would probably be something like (untested):

int (*f())(float);

Then for the rest you just need to keep adding parenthesis until it looks like lisp.

Upvotes: 6

Blindy
Blindy

Reputation: 67382

The same as with the typedef, only you place your function definition in place of its name.

Here's how f2 would look like:

typedef int (*(*f2)(double))(float);

You can do f3 as an exercise, since I'm assuming this is homework ;)

Upvotes: 7

Related Questions