I have the fortunate privilege to be part of the ISO C Standard mailing list, and recently a thread kicked off about Lambdas and what their need is in the C Community. That thread was in response to an ongoing push by Jens Gustedt’s proposal N2736, where Gustedt is building steam to put a proper function + data type into the C Standard at some point. What kicked off in that thread was a lot of talking about nested functions, blocks, statement expressions, whether we even need the ability to have data + code in C, and more. Several times I wrote a feature-length film of a response to that mailing list, but since the misconception seems to spread far beyond just the C Committee I decided I would publish my analysis of Lambdas, Nested Functions, and Blocks.
I will do my best to cover each of the solutions in pretty good detail. First, let’s start with where we are today, with how we have to do function calls and callbacks with plain, vanilla, Standard C code.
Plain C
Not too much to say here, except that this is the baseline we’ll be “scoring” each of the alternatives against. Using functions and handling associated data with code usually requires a tiny bit of API sculpting, but nothing that’s really overtly special since C99. Here’s a basic example:
int compute_with ( int x , int ( * user_func )( int )) { int completed_work = ( x + 2 ) * 3 ; return user_func ( completed_work ); } int f ( int y ) { return 1 + y ; }; int main () { return compute_with ( 1 , f ); }
Let’s say we compute some extra data in main , called x . If we want to have extra data to our user_func here, we have to add an extra little pizazz to our compute_with function. So, we add the “omni” parameter type void* into the argument list:
typedef int ( compute_func_t )( int , void * ); int compute_with ( int x , compute_func_t * user_func , void * user_data ) { int completed_work = ( x + 2 ) * 3 ; return user_func ( completed_work , user_data ); } struct variables { int x ; }; int f ( int y , void * user_data ) { struct variables * p_variables = ( struct variables * ) user_data ; return p_variables -> x + y ; } int main () { int x = 1 ; struct variables vars = { x }; return compute_with ( 1 , f , & vars ); }
This is the most generic form. In the above example, you could cut out the structure entirely and just pass a single pointer to x . But, this is normally how these sorts of callbacks are handled. It happens everywhere: epoll, libjansson, POSIX, Win32 API, whatever. The formula is always the same: make a struct , copy everything into said struct , and fire it off to the function as a pointer. Things get marginally more complicated if you have an asynchronous API, where the lifetime of the operation may outlive what you control directly in main or any other function. The solution there is to put things on the heap, with malloc or similar:
#include
Alright. So in the context of these three examples, we have a good idea of what we might be looking for. There are obviously far more complex arrangements and setups that can be performed, but this is about as illustrative of the typical function patterns as we can get. We won’t talk about every example for every implemenation extension below, but we will talk about the merits of each design as we go along!
... continue reading