Reputation: 8487
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
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:
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
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
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
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
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
Reputation: 208323
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
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