Tip 43: Wrap substructures in parent structures
26 December 11. [link] PDF version
level: You have lots of structures floating around
purpose: quickly jump around your hierarchy of types
Part of a series of tips on POSIX and C. Start from the tip intro page, or get 21st Century C, the book based on this series.
As per tip 39, inheritance is obnoxiously simple in C: use substructures. If you have base_struct which is somehow fixed and immutable, but you want to add information to it, then set up a new struct with bonus features:
typedef struct { base_struct base; color_struct now_with_color; audio_struct whizz, bang; } better_struct;
OK, you're done: now when you allocate a better_struct
, let us name it bb, you can
use the original library of operations on bb.base as before.
What's the syntax for multiple inheritance and mixns? Add as many base structs to the new struct as you wish.
Apophenia does this: the apop_data
struct is a gsl_matrix
and a
gsl_vector
plus bells and whistles, which means that you can allocate an
apop_data
set ad and use the GNU Scientific Library's full arsenal on
ad->matrix
or ad->vector
. That is, using functions that call the base
struct is trivial.
The other direction can be an annoyance: you've got a gsl_matrix
, and would
like to use a function that requires a apop_data
set with as little hassle as
possible. How can you get the system to allocate the requisite extra stuff around the
matrix that you care about?
Designated initializers, of course.
Apophenia has a wrapper to take dot products, because I hate the BLAS syntax (Basic Linear Algebra Subroutines--and for needs in the present day, is it ever basic).
#include <apop.h> int main(){ //Let us allocate a matrix & vector via designated initializers. //It's not easy: more on this below. gsl_vector *v1 = &(gsl_vector){.size=3, .stride=1, .data = (double[]){1, 2, 3}}; gsl_matrix *m1 = &(gsl_matrix){.size1=3, .size2=3, .tda=3, .data = (double[]){1, 0, 0, 0, 0, 1, 0, 1, 0 }}; //wrap the matrix and vector in apop_data structs. //This is easy. printf("method 1:\n"); apop_data *d1 = &(apop_data){.matrix=m1}; apop_data *d2 = &(apop_data){.vector=v1}; apop_data *out = apop_dot(d1, d2); apop_data_show(out); //or even easier: printf("method 2:\n"); out = apop_dot( &(apop_data){.matrix=m1}, &(apop_data){.vector=v1}); apop_data_show(out); //Or, via macros #define Data_from_matrix(m) &(apop_data){.matrix=(m)} #define Data_from_vector(v) &(apop_data){.vector=(v)} out = apop_dot(Data_from_matrix(m1), Data_from_vector(v1)); printf("method 3:\n"); apop_data_show(out); }
Making this happen actually took some planning, because there are now two ways to allocate
the structure: you could use the official allocation function, or you can use designated
initializers to pop a struct into existence. We generally don't like having two ways to do
something, but for a struct explicitly designed to wrap an already-useful structure, it's
hard to say no to such easy wrapping. Which is where the planning comes in: functions that
take in the structure need to accept a fully-allocated data structure or a structure that is
mostly empty. For the apop_data
, it's not such a problem, because functions have an
eye out for inputs which are mostly zero. The gsl_matrix
and _vector
,
however, don't gracefully deal with having certain basically internal elements, the tda
and
stride
, at zero.
[Previous entry: "Tip 42: Don't bother with union"]
[Next entry: "Tip 44: Brevity is the soul of incomprehensibility"]