Jump to content

  • Log In with Google      Sign In   
  • Create Account

Did you know you can define functions like that in C?

  • You cannot reply to this topic
12 replies to this topic

#1 pitchblack00   Members   -  Reputation: 174

Like
9Likes
Like

Posted 06 December 2013 - 05:25 PM

We all know the fearsome syntax for defining function pointers and function types, the one with the confusing parenthesis. You can find lot's of examples on the net. But here's a refresher.

 

For example, here's a type of a function taking an integer and returning an integer:
typedef int (foo_type)(int);
Type of a pointer to the same function:
typedef int (*foo_ptr_type)(int);

And here's a type of a another function that returns a pointer to the above function:

typedef foo_ptr_type (foo_getter_type)(char*);
Or this, also valid:
typedef foo_type* (foo_getter_type)(char*);

It is possible to unwrap above typedef, using a pretty obscure syntax(this is still pretty well known):

typedef int (*(foo_getter_type)(char*))(int);

Now can you guess how you would define a function that has the same type as int (*(foo_getter_type)(char*))(int)? No?

You just add brackets and give a name to char* argument: tongue.png

int (*(foo_getter)(char* name))(int)
{
  // I can't believe this thing compiled...
}

At first the function looks like it returns an int, but really it's returning a function that returns an int, go figure. I suspected this feature was there after I had a leftover "(int)" in my function declaration and compiler reported "cannot declare a function that returns a function". What? You can do that? So i started trying things out and discovered this syntax. I paged through K&R and couldn't find a mentioning that it's even possible, searched through net and could't find anything similar, so I guess this is one of the lesser known dusty corners of C that even language designers forgot they added it in there...

 



Sponsor:

#2 SeanMiddleditch   Members   -  Reputation: 7192

Like
1Likes
Like

Posted 06 December 2013 - 10:04 PM

This is something second-year students at my alma mater are expected to learn in depth if they hope to actually pass their third semester. The parsing rules for declarations in C are rather simple once you get them down.
 
Note of course that this is all much easier with typedefs and is by far the recommended way for a C programmer to do something like this:
 
typedef int(*IF)(void);

extern IF make_function(void);
 
In C++11 this all gets even easier using the new using syntax as you no longer have to remember where the name goes inside the typedef:
 
using IF = int(*)();

IF make_function();


#3 Trienco   Crossbones+   -  Reputation: 2224

Like
0Likes
Like

Posted 06 December 2013 - 11:20 PM

Same here. They had us do a lot of absurd declarations no sane person will ever need, just for practice. "Declare a function that returns an array of function pointers and takes a pointer to an array of int". Looking back, I think all they really wanted to see was who would be smart enough to break it down into multiple typedefs and who would be insane enough to do it all in one line.


f@dzhttp://festini.device-zero.de

#4 pitchblack00   Members   -  Reputation: 174

Like
0Likes
Like

Posted 06 December 2013 - 11:33 PM

That's right, declarations I knew about as well, but this is definition. Anyway certainly not something I'd like to find in my code base, but that goes to my personal zoo of craziest C gibberish.



#5 ultramailman   Prime Members   -  Reputation: 1585

Like
1Likes
Like

Posted 07 December 2013 - 12:39 AM

I've typedefed function pointers before, but never a function type. What can one do with the type of a function?



#6 frob   Moderators   -  Reputation: 22737

Like
0Likes
Like

Posted 07 December 2013 - 11:16 AM

I've typedefed function pointers before, but never a function type. What can one do with the type of a function?

 

 

The are used all the time in C++.  Many of the algorithmic functions take such a function. Functions that sort or search or generate or do something 'if' will all use them.

 

If you search through the C++ standard libraries you will find several hundred functions that take a predicate, binary predicate, unary function, binary function, or similar. 

 

These are just the same as the prototypes above, typedefs for functions of the form bool(*UnaryPredicate)(T), bool(*BinaryPredicate)(T,T), void(*UnaryFunction)(T), void(*BinaryFunction)(T,T) or similar. The typedef looks very similar to the ones in the original post, except they are templates.

 

The benefit is that you can say:  if(function(a,b)) and it just works.  An example is the std::for_each implementation:

template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, UnaryFunction function)
{
   while (first!=last) {
      function (*first);
      ++first;
   }
   return fn; // or, since C++11: return move(fn);
}

In C++ just go through the <algorithm> header, you will find a large number of function overloads taking "Predicate" or "Function" signatures. You will find similar overloads in most of the container classes, and in many other locations through the libraries.

 

 

When people talk about using Lambda functions, this is precisely where a Lambda function is used.


Check out my book, Game Development with Unity, aimed at beginners who want to build fun games fast.

Also check out my personal website at bryanwagstaff.com, where I write about assorted stuff.


#7 SiCrane   Moderators   -  Reputation: 9670

Like
0Likes
Like

Posted 07 December 2013 - 11:24 AM


These are just the same as the prototypes above, typedefs for functions of the form bool(*UnaryPredicate)(T), bool(*BinaryPredicate)(T,T), void(*UnaryFunction)(T), void(*BinaryFunction)(T,T) or similar. The typedef looks very similar to the ones in the original post, except they are templates.

Those are function pointer typedefs, not function typedefs.



#8 BGB   Crossbones+   -  Reputation: 1554

Like
0Likes
Like

Posted 07 December 2013 - 11:34 AM

 


These are just the same as the prototypes above, typedefs for functions of the form bool(*UnaryPredicate)(T), bool(*BinaryPredicate)(T,T), void(*UnaryFunction)(T), void(*BinaryFunction)(T,T) or similar. The typedef looks very similar to the ones in the original post, except they are templates.

Those are function pointer typedefs, not function typedefs.

 

 

you can make a pointer to a function type at least:

SomeFunctionType *foo;

 

not much sure of other uses.

would be cool if you could be like (in C):

SomeFunctionType Foo { ... }

or:

{

...

    Foo(SomeFunctionType { ... });   // make and pass closure here...

...

}

or something.

 

but, alas...

 

actually, it is sort of possible to do closures in plain C (without compiler extensions, and using plain function pointers), but tends to involve some amount of pain (and some bit of trickery in the background, and some restrictions), making it not really particularly viable as a general-purpose development practice.


Edited by BGB, 07 December 2013 - 11:39 AM.


#9 pitchblack00   Members   -  Reputation: 174

Like
0Likes
Like

Posted 07 December 2013 - 12:08 PM


would be cool if you could be like (in C):
SomeFunctionType Foo { ... }

 

I think the reason why function types can't be used to define functions is because there would be no way to assign argument names, so they are pretty useless by themselves, you can't return them, you can't assign them, but you can construct other types with it.



#10 rip-off   Moderators   -  Reputation: 8745

Like
0Likes
Like

Posted 07 December 2013 - 02:07 PM


What can one do with the type of a function?

Make std::function reasonable?



#11 ultramailman   Prime Members   -  Reputation: 1585

Like
1Likes
Like

Posted 07 December 2013 - 03:14 PM

Yeah, it would be cool to be able to do closures in c. When i saw that function typedef (not the function pointer typedef), it almost looked like one could declare a variable of a function type, and assign some value to it. Then I recall variables need a size, and have no answer to that.

 

So in c, other than declaring a variable as a pointer to a function type, there doesn't seem to be much use for a function type (it does look nice though, I like function_type * more than function_type_ptr).

 

edit:

Seems like it can be used to replace a forward declaration too, like this

#include <stdio.h>
typedef int main_func_t(void);
main_func_t test_func;
int main(void) {
    main_func_t * test_func2 = &test_func;
    test_func();
    (*test_func2)();
    return 0;
}
int test_func(void){
    puts("hello");
    return 0;
}

Edited by ultramailman, 08 December 2013 - 03:36 AM.


#12 BGB   Crossbones+   -  Reputation: 1554

Like
0Likes
Like

Posted 07 December 2013 - 04:34 PM

 


would be cool if you could be like (in C):
SomeFunctionType Foo { ... }

 

I think the reason why function types can't be used to define functions is because there would be no way to assign argument names, so they are pretty useless by themselves, you can't return them, you can't assign them, but you can construct other types with it.

 

 

they could, potentially, retain their argument names from the original typedef:

typedef int (FooFunc)(int x, int y);

 

FooFunc foo0

    { return(x+y); }

 

but, then again, this is also along the lines of wishing to be able to do something like:

void foo(va_list args...)

    { ... }

 

basically, to not require the usual va_start / va_end or the need to have an argument before the elipses (it doesn't ask "that" much, as on many targets, va_list support essentially needs to be built into the compiler anyways).

 

 

Yeah, it would be cool to be able to do closures in c. When i saw that function typedef (not the function pointer typedef), it almost looked like one could declare a variable of a function type, and assign some value to it. Then I recall variables need a size, and have no answer to that.

 

So in c, other than declaring a variable as a pointer to a function type, there doesn't seem to be much use for a function type (it does look nice though, I like function_type * more than function_type_ptr).

 

yeah.

 

several compilers (GCC, Clang, ...) offer closures as extensions, but nothing is supported in standard C.

 

 

in the time back when I had a (sort-of) working C compiler, it had closures, which were full closures (technically a bit closer to JS syntax though).

the main difference (from those provided by GCC) was that basically they were implemented using allocated memory-objects and involved generating an internal runtime call (and would actually quietly fold off closed-over variables into a heap-allocated structure).

 

(the C compiler sub-project eventually died mostly due to it being fairly slow and very buggy...).

 

 

as-is (in my case), it can sort of be done now (with standard C) using API calls (these calls are specific to my codebase), but requires basically putting the closed-over state into a struct, basically like:

typedef int (*foo_closure)(int, int);

struct closure_state_s { int z; };

int closure_function(struct closure_state_s *env, int x, int y)
    { return(x+y+env->z); }

foo_closure SomeFunctionReturningClosure(int z)
{
    struct closure_state_s *env;
    env=gcalloc(sizeof(struct closure_state_s));
    env->z=z;
    return((foo_closure)dyllWrapClosure(closure_function, env, "(ii)i"));
}

and with some annoying drawbacks:

with this interface, it is necessary to manually free the closures and environments later (though they will be reclaimed by the GC on x86-based targets);

the pool is finite-sized on ARM (with a relatively small number of closure-handles available, *);

there is a certain amount of cost in creating them (involves allocating memory and generating the relevant machine-code for the stub);

as can be noted from the code, it is sort of a hassle;

...

 

*: the ARM version uses a plain-C implementation, which comes at a bit of a cost in terms of performance, and requires essentially procedurally generating a big mass of code which basically read-off the argument list and then invoke a "generic function-dispatch" mechanism (basically, a gigantic procedurally generated "switch()"), making the whole thing a bit bulky and slow. there may also only be a small number of available function-handles available for a given set of argument and return types.

 

the x86 and x86-64 versions are a bit faster though, as they dynamically generate machine-code stubs which more directly marshal the arguments lists.

there are restrictions though on certain targets (Linux x86-64), mostly in that the particularly complex ABI means that some cases are not handled (my stuff generally only supports a subset of the SysV/AMD64 ABI, using "simplified" rules in a lot of edge-cases, which while not generally an issue, mean that some potential argument lists (generally those involving passing/returning structs) may fail potentially violently).

 

generally, argument lists containing primitive types and/or pointers and with fewer than 6 arguments are fairly safe though (this is what is supported by the plain-C version), though the generated-machine-code versions are a bit more capable (pretty much arbitrary argument lists are supported, and there is no specific limit as to the available number of closures).

 

this leaves their main use-case mostly for trying to optimize cross-language API calls (mostly C->script calls), which (as-such) are still fairly expensive (at present, often around 200-500 clock cycles, though mostly due to API issues, and with a lot of "generic C interpreter logic" getting in the way. getting it faster would likely involve making the C<->script interface look and behave a bit more like COM+ or similar, with calls more directly into the compiled script code...).

 

 

or such...


Edited by BGB, 07 December 2013 - 07:52 PM.


#13 Krohm   Crossbones+   -  Reputation: 3253

Like
0Likes
Like

Posted 09 December 2013 - 02:18 AM

I admit I missed a few. BTW, I am extremely worried this is considered something expected to be learnt at priori. It clearly has little reason to be used in general case and I believe those shall be buried forever. Type declarations in C are so easy some *nix ship with a command to decipher them.


Same here. They had us do a lot of absurd declarations no sane person will ever need, just for practice. "Declare a function that returns an array of function pointers and takes a pointer to an array of int". Looking back, I think all they really wanted to see was who would be smart enough to break it down into multiple typedefs and who would be insane enough to do it all in one line

No matter what they wanted to do. What they did, in reality is that some idiot will do that in the real world. Typical academical bullshit. They promote elitarims and production of indecipherable code: should be considered unacceptable by all means.





PARTNERS