5

What is the difference between these 2 declarations:

double math_operation(double x, double (*func)(double));
double math_operation(double x, double func(double));

They both seem to work with the same exact call in GCC:

math_operation(2.0, sqrt);

Is it just syntactic sugar or is there more to it?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
DarkAtom
  • 2,589
  • 1
  • 11
  • 27
  • Also: `typedef double (*dd_func)(double);` then `double math_operation(double, dd_func);` – Milag Jun 09 '20 at 21:15
  • @Fredrik - The second one is *very much* C. Function declarations enjoy the same treatment as array declarations in parameter lists. They decay. – StoryTeller - Unslander Monica Jun 09 '20 at 21:16
  • @Milag I am not asking for advice on how to declare parameters, I was just asking from a theoretical (and pedantic) standpoint. I know how ugly function pointers can get in real code :) – DarkAtom Jun 09 '20 at 21:17
  • 2
    @DarkAtom - You shouldn't allow yourself to be led astray by someone whose not willing to post an answer where it may be peer reviewed and voted upon. – StoryTeller - Unslander Monica Jun 09 '20 at 21:17
  • @StoryTeller-UnslanderMonica Any idea why the first form is prevalent? While the second one seem much more readable and clean. – Eugene Sh. Jun 09 '20 at 21:19
  • @EugeneSh. - I imagine it's because we all learned C the same way. Monkey C, monkey do. Then it becomes a habit. – StoryTeller - Unslander Monica Jun 09 '20 at 21:19
  • 3
    @EugeneSh. Because it creates a lot of confusion. If you declare a variable `double (*func)(double);` inside a scope body, it is a function pointer. If you declare `double func(double);`, it is a forward declaration of `func()`. The second form in parameter declarations is more readable, but is very strange to me. – DarkAtom Jun 09 '20 at 21:21
  • @DarkAtom - I think far more confusion grows out of arrays decaying. There's hardly anyone who ever learned C that didn't stumble upon trying to pass multi-dimensional arrays as pointers to pointers. The discrepency in function declarations is far more negligible in its potential confusion. – StoryTeller - Unslander Monica Jun 09 '20 at 21:23
  • 2
    The first version is the way it was done in traditional C. Making the indirection symbol optional was added later. Personally I don't like the way they support clearly different declarations as if they were the same. It would be preferable if they only supported a single, consistent format. – Tom Karzes Jun 09 '20 at 21:24
  • @StoryTeller-UnslanderMonica That's true, but once you learn it you can very much pray nothing changes. I guess that's what we can expect from a language developed in the 70s. – DarkAtom Jun 09 '20 at 21:25
  • @StoryTeller-UnslanderMonica: This business about using pointers-to-pointers for multidimensional arrays is strange. I do not recall seeing it before 2000, maybe later. Is anybody really using it in high-quality production code, or is it just something that caught root among amateurs or certain schools and has spread? When I learned C, and for some time later, there was no stumbling between arrays and pointers-to-pointers because people were not using pointers-to-pointers for multidimensional arrays. – Eric Postpischil Jun 09 '20 at 21:33
  • @EricPostpischil - I've never seen it in production quality code. Which is not surprising, because we learn it's not correct pretty fast. I learned C after the turn of the century, so I can't comment on how it was before. It may just be that myself and others I encountered were taught in a really flawed way. – StoryTeller - Unslander Monica Jun 09 '20 at 21:39
  • @EricPostpischil I guess it's because more and more people wanted multi-dimensional dynamically allocated arrays. The thing that is never taught in schools is that you can use pointers to VLAs for that: `int (*arr)[M] = malloc(N * M * sizeof(int));` (where N are rows and M are columns) – DarkAtom Jun 09 '20 at 21:40

1 Answers1

5

These two function declarations

double math_operation(double x, double (*func)(double));
double math_operation(double x, double func(double));

declare the same one function. You may include the both declarations in your program though the compiler can issue a message that there are redundant declarations.

The compiler implicitly adjusts a parameter having a function type to parameter of pointer type to the function.

On the other hand, a function designator used as an argument is converted to pointer to the function.

[Note: in general all these function declarations declare the same one function

double math_operation( double, double (*)( double ) );
double math_operation( double, double( double ) );
double math_operation( const double, double (*)( double ) );
double math_operation( const double, double( double ) );
double math_operation( double, double (*)( const double ) );
double math_operation( double, double( const double ) );
double math_operation( const double, double (*)( const double ) );
double math_operation( const double, double( const double ) );

Also the pointer to the function itself can have the qualifier const

double math_operation( double, double ( * const )( double ) );
double math_operation( const double, double ( * const )( double ) );
double math_operation( double, double ( * const )( const double ) );
double math_operation( const double, double ( * const )( const double ) );

-end note.]

From the C Standard (6.7.6.3 Function declarators (including prototypes))

8 A declaration of a parameter as ‘‘function returning type’’ shall be adjusted to ‘‘pointer to function returning type’’, as in 6.3.2.1.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Well this clarifies everything. It is just syntactic sugar, just like `int*` vs `int[]` as parameter. Thank you! – DarkAtom Jun 09 '20 at 21:18
  • @DarkAtom You are write. And a function designator used as an argument is implicitly converted to pointer to the function. – Vlad from Moscow Jun 09 '20 at 21:19
  • Well I get confuse by all of those `const`s now, but I get your point. But I think that for `const double` the compiler may make some optimizations so that the parameter is passed by reference and not by value (since it cannot change). – DarkAtom Jun 09 '20 at 21:27
  • @DarkAtom The qualifier constant is another story.:) If a parameter is declared as a constant it is ignored when the function type is determined.:) – Vlad from Moscow Jun 09 '20 at 21:29
  • So that means that such optimizations cannot be made then. I guess you can always use `const double*` for that. I guess it's the same story with `volatile`? – DarkAtom Jun 09 '20 at 21:32
  • @DarkAtom const double * is not a constant parameter. It is a non-constant pointer to constant data. A constant pointer will look like const double * const. – Vlad from Moscow Jun 09 '20 at 21:33
  • With `const X*` you are passing a reference (pointer) to X, and X cannot be modified through it. With `const X` you are copying X and not allowing the function to modify the copy. It's pointless to use the second form, that's what I was trying to say. Anyway, this is off-topic :) – DarkAtom Jun 09 '20 at 21:36
  • 1
    There are 4 more cases with `double math_operation(double, double (* const)(double ) );`... – KamilCuk Jun 09 '20 at 21:37
  • @KamilCuk You are right. I will update my answer.:) – Vlad from Moscow Jun 09 '20 at 21:45
  • (Well, a side note, there are x4 more cases with `volatile`, don't... ;) – KamilCuk Jun 09 '20 at 21:49
  • @KamilCuk I will stop at the listed declarations.:) – Vlad from Moscow Jun 09 '20 at 21:51