Data Structures | |
struct | CLOS_t |
Defines | |
#define | cu_clos_def(PREFIX, PROTOTYPE, STRUCT_BODY) ... |
#define | cu_clos_dec(PREFIX, PROTOTYPE, STRUCT_BODY) ... |
#define | cu_clos_fun(PREFIX, PROTOTYPE) |
#define | cu_clos_edec(PREFIX, PROTOTYPE, STRUCT_BODY) ... |
#define | cu_clos_efun(PREFIX, PROTOTYPE) |
#define | cu_clos_self(PREFIX) ... |
#define | cu_call(clop, args...) ... |
#define | cu_call0(clop) ... |
#define | cu_prot(result_type, arg_decl_list...) ... |
#define | cu_prot0(result_type) ... |
#define | cu_clop(clop, result_type, arg_decl_list...) ... |
#define | cu_clop0(clop, result_type) ... |
#define | cu_clop_null ... |
#define | cu_clop_is_null(clop) ... |
#define | cu_clop_def(clop, result_type, arg_decl_list...) |
#define | cu_clop_def0(clop, result_type) |
#define | cu_clop_edef(clop, result_type, arg_decl_list...) |
#define | cu_clop_edef0(clop, result_type) |
Typedefs | |
typedef IMPLEMENTATION_DEFINED | CLOS_clop_t |
Functions | |
CLOS_clop_t | CLOS_prep (CLOS_t *clos) |
CLOS_clop_t | CLOS_ref (CLOS_t *clos) |
This submodule implements a efficient and partly type-safe encapsulation of closures. The capsule contains a function pointer and an optional context. This achieves two goals. It standardises the idiom of passing a function pointer and a void *
of client data to functions, and it allows passing functions without the client data to the same function.
cu/i386/clos.h
. However, that seems to be highly inefficient, maybe because the processor must jump outside the text section of the program. Therefore, this platform-independent implementation is used, even if it is less convenient.The closures are implemented mostly with macros and some type hacks, so here is a usage-based documentation instead of the usual function-centered documentation.
A closure pointer (clop) is the modified function pointer type, where the closure data structure is abstracted out. A clop f is declared with
The special 0-argument form is needed due to limitations of the preprocessor. The closure is called with
As a callback for buffered reading, the type reader_t
defined as
typedef cu_clop(reader_t, ssize_t, void *buf, ssize_t len);
may be handy. A function to iterate over the character sequence produced by such a function may look like this:
void reader_iter(reader_t reader, cu_clop(cb, void, char ch)) { char buf[512]; ssize_t n_read; while ((n_read = cu_call(reader, buf, 512)) > 0) { size_t i; for (i = 0; i < n_read; ++i) cu_call(cb, buf[i]); } }
A global variable,
extern cu_clop(global_error_handler, void, char const *msg);
To call this, use
cu_call(global_error_handler, "Some message");
In my experience, the most useful way to create closure pointers is from a static definition with closure data. This is done with
cu_clos_def(PREFIX, cu_prot(result_t, arg1_t arg1, ..., argn_t argn), ( local0_t local_var_0; // This is the body of a struct local1_t local_var_1; // definition which is typedef'ed as ... )) // PREFIX_t. { cu_clos_self(PREFIX); // Declare and set the self variable. // Function definition. The special variable self is a // pointer to the closure struct data, so here you can // use self->local_var_0, etc. }
which creates the type PREFIX_t
which is a typedef for a structure with the given local variables. An instance of the closure can now be declared as PREFIX_t clos
, local variables assigned with clos.local_var_n = ...
, and the closure referred to by PREFIX_prep(&clos)
which returns a cu_clop(, result_t, arg1_t, ..., argn_t)
pointer. PREFIX_prep
will also initialise the function pointer which is hidden at the start of PREFIX_t
.
For closures taking no arguments, use cu_prot0
instead of cu_prot
above.
The following function sums up the value slots of a cucon_pmap_t
, assuming they contain inlined double
values:
cu_clos_def(sum_callback, cu_prot(void, void const *key, void *value), ( double sum; )) { cu_clos_self(sum_callback); self->sum += *(double *)value; } double sum_double_valued_pmap(cucon_pmap_t pmap) { sum_callback_t cb; cb.sum = 0.0; cucon_pmap_iter_mem(pmap, sum_callback_prep(&cb)); return cb.sum; }
If you want to return the closure from the function it is created you better allocate it dynamically:
typedef int cu_clop0(counter_t); cu_clos_def(counter_callback, ( int value; )) { cu_clos_self(counter_callback); return self->value++; } counter_t make_counter(int start) { counter_callback_t *cb = cu_gnew(counter_callback_t); cb->value = start; return counter_callback_prep(cb); }
The counter can now be used as
Closure pointers:
cu_clop(CLOSURE_POINTER, RESULT_TYPE, ARG1_TYPE, ..., ARGn_TYPE); cu_clop(CLOSURE_POINTER, RESULT_TYPE); RESULT_TYPE result = cu_call(CLOSURE_POINTER, ARG1, ..., ARGn); RESULT_TYPE result = cu_call0(CLOSURE_POINTER);
Real closures: Define in static file scope with
cu_clos_def(CLOS_NAME, cu_prot(RESULT_TYPE, ARG1_TYPE a1, ..., ARGn_TYPE am), ( LOCAL_VAR1_TYPE V1; ...; LOCAL_VARm_TYPE Vm; )) { cu_clos_self(CLOS_NAME); FUNCTION_BODY }
Use cu_prot0(RESULT_TYPE) for empty argument list. This can be converted to a closure pointer using the pattern
CLOS_NAME_t CLOS; // type defined by cu_clos_def CLOS.V1 = ...; // initialise variables ...; CLOS.Vm = ...; CLOSURE_POINTER = CLOS_NAME_prep(&CLOS);
Function adapters: TODO
The extensive use of macros can make errors uninformative. Things to check:
cu/clos.h
is included and that the macros are spelled correctly. #define cu_call | ( | clop, | |||
args... | ) | ... |
Calls clop with arguments args. If there are no argumets, use cu_call0 instead.
#define cu_call0 | ( | clop | ) | ... |
Calls clop with no arguments.
#define cu_clop | ( | clop, | |||
result_type, | |||||
arg_decl_list... | ) | ... |
Expands to a declaration of clop as a closure pointer with result type result_type and argument declarations arg_decl_list which may optionally contain argument names. Use cu_clop0 if N is 0. You can add linkage or typedef
in front of this declaration. When used in a cast, you can drop the clop, but leaving the comma, eg (cu_clop(, void *, void *))equal
.
#define cu_clop0 | ( | clop, | |||
result_type | ) | ... |
A 0-argument variant of cu_clop.
#define cu_clop_def | ( | clop, | |||
result_type, | |||||
arg_decl_list... | ) |
Declare a static clop pointer and initialise it with a function definition whose body follow this macro invocation. Example:
cu_clop_def(dump_int, void, int x) { printf("%d\n", x); } void dump_umap_keys(cucon_umap_t map) { cucon_umap_iter_keys(map, dump_int); }
#define cu_clop_def0 | ( | clop, | |||
result_type | ) |
A 0-argument variant of cu_clop_def.
#define cu_clop_edef | ( | clop, | |||
result_type, | |||||
arg_decl_list... | ) |
A variant of cu_clop_def which exports clop. Most useful if you also put a corresponding forward declaration in your header file:
extern cu_clop(clop, result_type, arg_decl_list...)
#define cu_clop_edef0 | ( | clop, | |||
result_type | ) |
A 0-argument version of cu_clop_edef.
#define cu_clop_is_null | ( | clop | ) | ... |
Check if clop is the dedicated invalid closure pointer.
#define cu_clop_null ... |
A dedicated invalid closure pointer. Can not be called. Preferably use cu_clop_is_null rather than ==
or !=
to check if a closure pointer holds this value.
#define cu_clos_dec | ( | PREFIX, | |||
PROTOTYPE, | |||||
STRUCT_BODY | ) | ... |
Forward declaration of a static closure struct. The arguments are the same as cu_clos_def, but no function body shall follow. The function itself is defined with cu_clos_fun.
#define cu_clos_def | ( | PREFIX, | |||
PROTOTYPE, | |||||
STRUCT_BODY | ) | ... |
Starts the definition of a static closure. It is followed by the actual function body in the usual form of a brace-enclosed block. The PROTOTYPE must be specified with cu_prot or cu_prot0, and must contain parameter names, which are local to the function body. STRUCT_BODY is the body of the closure struct, enclosed in parentheses. The function body should start with cu_clos_self(PREFIX) declaration, which makes sure a local variable self is declared and initialised to point to the closure struct.
The following example defines a closure for printing comma-separated integers.
cu_clos_def(print_int, cu_prot(void, int x), (FILE *out; int done_cnt;)) { cu_clos_self(print_int); fprintf(self->out, "%s%d", self->done_cnt++ == 0? "" : ", ", x); } int main() { print_int_t f; f.out = stdout; f.done_cnt = 0; cu_call(print_int_prep(&f), 4); cu_call(print_int_ref(&f), 5); cu_call(print_int_ref(&f), 6); fputc('\n', stdout); return 0; }
#define cu_clos_edec | ( | PREFIX, | |||
PROTOTYPE, | |||||
STRUCT_BODY | ) | ... |
Forward declaration of an external closure struct. The arguments are the same as cu_clos_def, but no function body shall follow. The function itself is defined with cu_clos_efun.
#define cu_clos_efun | ( | PREFIX, | |||
PROTOTYPE | ) |
Provide the function body of a closure which was forward declared with cu_clos_edec. See cu_clos_def for details on the function body.
#define cu_clos_fun | ( | PREFIX, | |||
PROTOTYPE | ) |
Define the function body of a closure which was forward declared with cu_clos_dec. See cu_clos_def for details on the function body.
#define cu_clos_self | ( | PREFIX | ) | ... |
Use this in the start of a closure body to declare and assign a variable self as a pointer to a struct containing the local variables of the current closure.
#define cu_prot | ( | result_type, | |||
arg_decl_list... | ) | ... |
This is used in some contexts to group macro arguments which specifies a prototype. It is needed when a variable-sized argument sequence is not at the end of a macro call, such as with cu_clos_def. Use cu_prot0 if there are no arguments.
#define cu_prot0 | ( | result_type | ) | ... |
A 0-argument variant of cu_prot.
typedef IMPLEMENTATION_DEFINED CLOS_clop_t |
A typedef emitted by the invocation of cu_clos_def with prefix CLOS. This is the closure pointer type.
CLOS_clop_t CLOS_prep | ( | CLOS_t * | clos | ) |
Creates a closure pointer from a closure struct pointer. In the process, the private field of clos is initialised. Here CLOS is the prefix given as the first argument of the cu_clos_def definition.
CLOS_clop_t CLOS_ref | ( | CLOS_t * | clos | ) |
Returns the closure pointer of clos assuming it has been prepared in advance with CLOS_prep. Here CLOS is the prefix given as the first argument of a cu_clos_def definition.