39

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.

glglgl
  • 89,107
  • 13
  • 149
  • 217
keveman
  • 8,427
  • 1
  • 38
  • 46
  • 3
    C or C++? Because C++ has some easier ways to express such things. – Puppy May 25 '12 at 17:27
  • 7
    [cdecl is your friend](http://cdecl.ridiculousfish.com/?q=declare+f3+as+pointer+to+function+%28int%29+returning++pointer+to+function+%28double%29+returning+pointer+to+function+%28float%29+returning+int) – Robᵩ May 25 '12 at 17:43
  • 1
    cdecl.org only translates from c-syntax to English. It's not very useful for going the other way, since you have to word your declaration perfectly in the form that it expects, and that's not how English or any other natural language works. – Benjamin Lindley May 25 '12 at 17:48
  • 1
    Agreed. I use a specific strategy to help with that. I start with a simple declaration that I know is valid. `explain` then creates an English-language form for that declaration. I copy-paste and carefully extend that English-language form, add the verb `declare`, and, *voila*, I've got the answer I seek. – Robᵩ May 25 '12 at 18:20
  • You should be using [`std::function`](http://stackoverflow.com/questions/3534812/how-does-template-parameter-of-stdfunction-work-implementation) instead... the same reason you should use `std::vector` instead of `new`/`delete`. – Inverse May 28 '12 at 05:15
  • 1
    And for extra credit make f3 __stdcall, f2 __cdecl and f1 __fastcall – Gur Jun 14 '14 at 12:38

7 Answers7

144

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
John Bode
  • 119,563
  • 19
  • 122
  • 198
  • 10
    +1, as this really answers the original question, which was about the correct syntax in C... – Macmade May 25 '12 at 18:30
  • 1
    This answer should be mass-printed and distributed in computer engineering schools, it's the simplest and best way to understand function pointers declaration syntax, thank you John Bode ! – Mouradif Jul 22 '16 at 13:37
  • Thanks, John! I was wondering where you have learned that? Is there some good book which explains it? – Tim Aug 11 '17 at 20:07
  • 1
    @Tim: It's mainly a matter of understanding declaration syntax, which unfortunately is not explained very well in most references. It just kind of "clicked" one day, and suddenly hairy declarations made sense. – John Bode Aug 12 '17 at 03:13
  • Neat. So this is the CFG rule that is actually used. One could have thought that the C language designer would have opted for a recursive type-specification at the front for the return type, but he instead chose a recursivity-safe enclosing syntax. Seems fine to me and I would like to take that into account for my language design, just to keep compatibility with C habbits. – rplgn Nov 24 '21 at 20:38
  • 1
    @rplgn: This falls out of the "declaration mimics use" paradigm. The structure of a declarator is the same as the structure of an expression of the same type in the code. For example, if you have an array of pointers to `int` and you want to get the value of the `i`'th element, you index into the array and deference the result - `x = *a[i];`. The type of the *expression* `*a[i]` is `int`, so the declaration of `a` is written `int *a[N];`. The array-ness and pointer-ness are functions of the declarator, not the type specifier. – John Bode Nov 29 '21 at 16:30
  • @JohnBode I am glad that you have mentioned that paradigm. I find this declaratory syntax to be the most complicated lexical ruleset of that language. Consider how C++ took over this ruleset for compatibility but has since then not created a more complicated one. Quite arguably that paradigm does boost learnability, but since this lexical rule for pointer-to-function return-type falls out it is **really hard to get used to**. It can look really **ugly**, too. But that's what people have adapted to, so fair enough. – rplgn Nov 30 '21 at 17:22
15

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;
Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 1
    The question was about C syntax, even if the post was tagged with C++, so it's not relevant IMHO... But nice answer by the way. : ) – Macmade May 25 '12 at 18:31
  • 1
    This is perhaps cleaner than not using any aids whatsoever (neither `typedef` or `add_pointer`), but the `typedef` approach is still cleaner (IMHO). – David Hammen May 25 '12 at 19:31
  • 8
    @Macmade: You tag C++, you get a C++ answer. – Puppy May 28 '12 at 20:17
7

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

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • 1
    The answer you give is readily available using a google search. However, the syntax for more than one level (function returning function pointer returning function pointer) is not very easy to find. And the homework reference was unwarranted. – keveman May 25 '12 at 17:38
  • 2
    It's exactly the same, replace `f2` in my form with your `f3` typedef. Not sure what you mean by Google search, it's basic C syntax.. – Blindy May 25 '12 at 17:38
  • 1
    search for "function returning function pointer" and you will find the answer you just posted. However, search for "function returning function pointer returning function pointer" and you wouldn't find the answer you are looking for. – keveman May 25 '12 at 17:40
  • 2
    @keveman: He gave you a very simple rule to follow that should allow you to construct any form you want. Why are you complaining that google also gives you part of the answer? – Benjamin Lindley May 25 '12 at 17:41
  • BTW, `int (*(*f3(double))(float))(int) { return 0; }` defines the function with the type I am looking for. Thanks. – keveman May 25 '12 at 17:42
  • 1
    @keveman- That definition doesn't match your original description. Enter it into cdecl.org and you'll get a detailed description. I think your function arguments need to be in the order `int`->`double`->`float`. – bta May 25 '12 at 17:50
6

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
5

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.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
2

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.

rplgn
  • 111
  • 7
  • 1
    Welcome to Stack Overflow. Please read [answer]. Images can be used to enhance answers, but answers should be accessible (useful without the images). – ChrisGPT was on strike Nov 25 '21 at 01:10
  • @Chris Hello, I am sorry that my insights have not been explained in too much detail. The answer is heavily based on internal research. I do appreciate your constructive criticism but I don't see how you not understanding the picture does warrant that everybody does not find it accessible. It could be more constructive to me if you asked a question about the picture to help me improve the answer. Otherwise I am asking myself how this StackOverflow community does value creative contributions to the complicated debate of scientific software development. – rplgn Nov 25 '21 at 10:03
  • It's not that I don't _understand_ the picture, it's that images are not [accessible](https://en.wikipedia.org/wiki/Accessibility). They also can't be indexed by search engines, and a whole bunch of other things. Answers should always be useful even without images. Again, please read [answer]. – ChrisGPT was on strike Nov 25 '21 at 12:15
  • @Cris First off, thank you for helping me straighten out my answer to this important question. Your time and effort are what make this community great. I want to inform you that I have read the article that you have provided to me. Curiously enough there is no mention of search engines or images being unaccessible. Thus I can solely rely on your guidance and it was an inevitable awkward situation not explained to the possible newcomer (which I really am not, but it may seem true in your statistical perspective). – rplgn Nov 25 '21 at 12:41
  • 1
    You're right, that link doesn't mention images. You might find [this meta post about posting images of code](https://meta.stackoverflow.com/a/285557/354577) more relevant. Yes, that post is about _code_, but all of the same arguments apply here. – ChrisGPT was on strike Nov 25 '21 at 12:44
1

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;
Inverse
  • 4,408
  • 2
  • 26
  • 35