CSpe

back to project summary
CSpe lets you create new C functions pointers at run time by specializing some (or all) of another function's arguments.

Some of the reasons this project exists:

Example 1

Let's say you want to iterate through a GLib Hashtable using g_hash_table_foreach() :
  void g_hash_table_foreach(GHashTable *hash_table,
                            GHFunc func,
                            gpointer user_data);
func will be called as func(key, value, user_data), so if you want to pass more arguments you usually end up creating a structure :

  void f(int x, int y,			// function we want called
	 gpointer key, gpointer value);

  typedef struct
  {
    int x;
    int y;
  } t_dummy_struct;

  void wrapper_function(gpointer key,
                        gpointer value,
                        gpointer data)
  {
    t_dummy_struct *s = data;
    f(s->x, s->y, key, value);
  }

  void iterate(GHashTable *hash_table,
               int x, int y)
  {
    t_dummy_struct s;
    s.x = x;
    s.y = y;

    g_hash_table_foreach(hash_table, wrapper_function, &s);
  }

needless to say it's painful to write (and incidentally dangerous, since the compiler can't check what you're doing with void pointers).
Using Cspe you could do this instead:

  void f(int x, int y,
	 gpointer key, gpointer value);

  void iterate(GHashTable *hash_table,
               int x, int y)
  {
    void (*func)(gpointer key, gpointer value); 
    // Note: a GHFunc also takes a gpointer user_data but we dont't use it here


    CSPE_4_2(func,                             // lvalue to store result in
             void, f, int, int, gpointer, gpointer,   // type of f()
             x, y);                            // specialize x and y args

    g_hash_table_foreach(hash_table, (GHFunc) func, NULL);
    CSPE_FREE(func);
  }

the CSPE_ macros are explained below.

Example 2

Let's say you're using a GLib Hashtable inside an object to map names to ids. You allocate the ids from a pool (each object has its own pool), and you want to ensure they get released automatically when removed from the hash table. g_hash_table_new_full() lets you specify a value destroy function, which seems to fit the situation perfectly:
value_destroy_func: a function to free the memory allocated for the value used when removing the entry from the GHashTable
The problem is it has to be of type void (*GDestroyNotify) (guint id) and there's no way to also pass the pointer to the pool.
Basically, unless you do some evil trickery with globals, you're screwed.
If you can do partial function application however, this is easy: you just specialize the pool argument in pool_dealloc(pool, id) and pass that function as value destroy function :

  #include <cspe-1.0/cspe.h>

  t_pool *	pool_create(void);
  void		pool_destroy(t_pool*);
  void		pool_dealloc(t_pool *pool, int id);

  void object_init(object *obj)
  {
    void (*value_destroy)(int id);

    obj->pool = pool_create();

    CSPE_2_1(value_destroy,                    // lvalue to store result in
             void, pool_dealloc, t_pool*, int, // type of pool_dealloc()
             obj->pool);                       // specialize pool argument

    obj->hash_table = g_hash_table_new_full(g_str_hash, g_str_equal,
					    NULL, value_destroy);
    obj->value_destroy = value_destroy; // we need to free it later
  }

  void object_destroy(object *obj)
  {
    g_hash_table_destroy(obj->hash_table);
    CSPE_FREE(obj->value_destroy);
    pool_destroy(obj->pool);
  }

So the CSPE_2_1() macro is where all the magic happens:
at runtime it generates code in the heap and stores the address of that code in value_destroy. the generated asm is basically just a function call to pool_dealloc() hardcoding the specialized arguments, and passing the others along.

When we don't need the function anymore, we call CSPE_FREE() to release memory.

The first number in CSPE_2_1() is the number of arguments of the source function.
The second one is the number of arguments being specialized.
So if we had a function taking 5 arguments and we specialized 3 of them, we would use CSPE_5_3().

Providing the type of the source function and its arguments makes the macros a lot uglier to use, but gives you type safety :
you will get a warning if the types don't match.

Supported types

Unsupported types

Optimizations

Supported architectures

Links