10

Often I have to program microcontrollers in C, because C++ compilers are often not available, or can not make extremely small code because of various bugs. But often, OOP "syntactic sugar", is very convenient when it comes to making programs for hardware more clearly encapsulated for easy maintenance; so I wanted to find out if there was a way to do OOP syntax in C where as much as possible the OOP overhead (when not needed) could be made to optimize out in a way that is portable. eg: That will optimize with gcc targeted for different microcontrollers, or perhaps by using gcc's preprocessor and a generic ANSI-C compiler if gcc is not available for that microcontroller.

I found only threads, like this, Elegant way to emulate 'this' pointer when doing OOP in C? which generally do OOP by embedding pointers into structs but that's not always what I want because it wastes memory when I'm not interested in virtual methods, or anything like that. I can always follow the coding style in the link where those features are needed, but I want to develop techniques for when they are not needed; e.g. I just want to be able to program using OOP paradigms, with simple easy to understand code (Not necessarily C++, though I like C++), and still be able to achieve a minimal C program memory usage when some OOP paradigms are not in use.

So, I resorted to experimentation with gcc, and C99, because in general gcc 3.2 or above is available for most platforms; and realized that I could use the sizeof() and typeof() compiler functions from C99 to index classes automatically ( a 'trick' of sorts ) from an unused/uninitialized union member (So classes must be unions with sub-structs), in order to access a compile time constant lookup table created by macros, which could bind data and methods, and guarantee all type checking. etc. etc. etc.

eg: GCC allows the optimizing out of const structures, and arrays, when their members are only accessed as constant expressions, so I thought I might be able to use that to build a macro based compile time binding system where the OOP overhead is handled in GCC and actually optimizes out of the final binary.

With this system, I can now do variadic macro method calls, like: M( a , init, "with", "any", "parameters", 7 ) which looks up variable a's type, call method init, using variable number of parameters...

See code examples below, and try them out -- it's simpler than the explanation: Use gcc -E to see the macro expansions, and note for ANSI only compilers, the typeof() operator will have to be replaced by a (void*)typecast; type checking only works with GCC.

The code is cut and paste-able into a text editor, with filename on first line, and it does compile and run on normal PC systems.

Although I did succeed in getting rid of individual pointers in every struct to "point back to" a class's list of methods, which saves memory in a limited memory microcontroller, I wasn't quite able to figure out how to get the compiler to optimize out unused method pointers because I had to use (void*) pointers for the classes to hold them in an array, and those require a memory address (address of a struct) and a linker instance; and don't optimize out.

So: I was wondering if anyone knew of a way to improve my solution by making some kind of an initialized method struct which would optimize out (have no linker address) after compilation, eg: when it's members are only accessed as constant expressions in the code. In essence I'm needing to be able to look up an element in array where the initialized portion of each array element is a different classXXX_mt, rather than a list of addresses to classXXX_mt all typecast to (void*).

There's two other improvements I'd like help with if anyone can think of a simple solution; The cpp (c-pre-processor) doesn't allow defining of new macros from within a previous macro by token concatenation (As far as I know), so I have to make fixed length macro lists (A maximum of 10 in my example) to hold class definitions; which means I can only have a maximum of 10 classes in a program; but ideally, I would like a way to make my code more generic, so that the cpp could create variable length lists on the fly. eg: The problem is related the inability of the c pre-processor to "count" automatically.

And secondly, when I try to use anonymous structs for newer versions of GCC, so I might get rid of an extra 'm' required to access member data in ISO-C eg: foo.m.mydata, by deleting the 'm' name from the class union definition, and compile with gcc -std=c11 , it then simply gave me errors claiming the struct defined nothing... so, anonymous structs inside unions don't work even in GCC 4.8 although it supposed to; how can I get anonymous structs to work?

Below is the example of how I tested and implemented an include file, voidbind.h, which builds a list of classes and statically links the methods to the variables of that class type.

Ultimately, the system allows me to program like this example; which I compiled with gcc 4.0 to 4.9 with no problems:

//classtest.c
#ifndef MACROCHECK  // Don't macro expand stdio.h, it's ugly...
#include <stdio.h>  // to see macros, do gcc -D MACROCHECK -E classtest.c
#endif
#include "class1.h" // include example class, library.

#define _VOID_FINALIZE
#include "voidbind.h" // Make class list finalized, no more classes allowed

void main( void ) {
    class1_ct a; // types ending in _ct are the macro created class types
    class2_ct b;

    M( a , init ); // Call method of variable, a, and the function init.
    printf("a=%s %s\n",a.m.name, M( b, tryme, "echo is this" ) ); 
    // I'd love to be rid of .m. in the previous line using anonymous struct
}

Next is the Class definition / header file, for both class1 and class2, showing how the macro pre-processor is used to create classes of data bound to methods and the _ct type; normally this would probably be broken up into two header files, and two libraries; but I'm just abusing the header by putting all the code together for simplicity.

//class1.h
#ifndef _class1_h
#define _class1_h


// Define the data type structure for class1
typedef struct {
    char* name;
    int   one;
} class1_t;

// Define the method type structure for class1 
union class1_ctt ; // class type tag, incomplete tag type for class1_ct
typedef struct { // method prototypes
    void (*init)( union class1_ctt* ); // passed a pointer to class1_ct
} class1_mt;

// bind class1_mt and class1_t together into class1_ct
#define _VOID_NEW_CLASS class1
#include "voidbind.h"

// Begin class2 definition
typedef struct { // define data type for class2
    int x;
} class2_t;

union class2_ctt ; // class type tag, forward definition
typedef struct { // method prototypes for class2
    char* (*tryme)( union class2_ctt*, char* echo );
} class2_mt;

// bind class2_t and class2_mt together into class2_ct
#define _VOID_NEW_CLASS class2
#include "voidbind.h"

// --------------------------------------------- Start library code
// This would normally be a separate file, and linked in
// but as were doing a test, this is in the header instead...

//#include <class1.h>

void class1_init( class1_ct* self ) {
    self->m.name = "test";
    self->m.one=5;  
}

// Define class1's method type (_mt) instance of linker data (_ld):
// voidbind.h when it creates classes, expects an instance of the
// method type (_mt) named with _mt_ld appended to link the prototyped
// methods to C functions.  This is the actual "binding" information
// and is the data that I can't get to "optimize out", eg: when there
// is more than one method, and some of them are not used by the program

class1_mt class1_mt_ld = {
    .init=class1_init
};

// ----------- CLASS2 libcode ----

char* class2_tryme( class2_ct* self, char* echo ) {
    return echo;
}

// class2's method type (_mt) instance of linker data (_ld).
class2_mt class2_mt_ld = { // linker information for method addresses
    .tryme=class2_tryme
};

// --------------------------------------------- End of library code

#endif

Finally, comes voidbind.h This is the heart of the system, Getting the CPP to make a compile time constant list of void* pointers to method structs ... the void* list will always optimize out, as long as everything passed in are compile time constants. (But the structs in the list will not completely optimize out. :( even if constants. )

For this to idea to work, I had to figure out a way to make cpp count how many times the voidbind header file was #included, in order to automatically make a list of class pointers, and since the macro preprocessor can not do addition, or define macros which change based on a previous definition of the same macro name; I had to use inline functions to "save" the pointer to the class method struct (_mt) from one pass to the next. That's what forces me to basically use void* pointers, though it might be solvable in another way.

// voidbind.h
// A way to build compile time void pointer arrays
// These arrays are lists of constants that are only important at compile
// time and which "go away" once the compilation is finished (eg:static bind).
// Example code written by: Andrew F. Robinson of Scappoose


#ifdef _VOID_WAS_FINALIZED //#{
#error voidbind_h was included twice after a _VOID_FINALIZE was defined
#endif //#}

// _VOID_FINALIZE, define only after all class headers have been included. 
// It will simplify the macro expansion output, and minimize the memory impact
// of an optimization failure or disabling of the optimization in a bad compiler
// in hopes of making the program still work.

#ifdef _VOID_FINALIZE //#{
#define _VOID_WAS_FINALIZED
#undef _VOID_BIND
static inline void* _VOID_BIND( int x ) {
    return _VOID_BIND_OBJ[ x ];
}
#else

// Make sure this file has data predefined for binding before being
// included, or else error out so the user knows it's missing a define.

#if ! defined( _VOID_NEW_OBJ ) && ! defined( _VOID_NEW_CLASS ) //#{
#error missing a define of _VOID_NEW_OBJ or _VOID_NEW_CLASS
#endif //#}


// Initialize a macro (once) to count the number of times this file
// has been included; eg: since one object is to be added to the void
// list each time this file is #included. ( _VOID_OBJn ) 

#ifndef _VOID_OBJn //#{
#define _VOID_OBJn _ERROR_VOID_OBJn_NOT_INITIALIZED_

// Initialize, once, macros to do name concatenations 
#define __VOID_CAT( x, y ) x ## y
#define _VOID_CAT( x, y ) __VOID_CAT( x , y )

// Initialize, once, the empty void* list of pointers for classes, objs.
#define _VOID_BIND_OBJ (void* []){\
    _VOID_OBJ0() , _VOID_OBJ1() , _VOID_OBJ2() , _VOID_OBJ3() , _VOID_OBJ4()\
 ,  _VOID_OBJ5() , _VOID_OBJ6() , _VOID_OBJ7() , _VOID_OBJ8() , _VOID_OBJ9()\
}
// Define a function macro to return the list, so it can be easily
// replaced by a _FINALIZED  inline() function, later
#define _VOID_BIND(x) _VOID_BIND_OBJ[ x ]

// All void pointers are initially null macros.  So the void list is 0.
#define _VOID_OBJ0()  0
#define _VOID_OBJ1()  0
#define _VOID_OBJ2()  0
#define _VOID_OBJ3()  0
#define _VOID_OBJ4()  0
#define _VOID_OBJ5()  0
#define _VOID_OBJ6()  0
#define _VOID_OBJ7()  0
#define _VOID_OBJ8()  0
#define _VOID_OBJ9()  0
#endif //#}

// Figure out how many times this macro has been called, by
// checking for how many _VOID_OBJn() function macros have been
// replaced by inline functions

#undef _VOID_OBJn

#if defined( _VOID_OBJ0 ) // #{
#undef _VOID_OBJ0
#define _VOID_OBJn 0
#elif defined( _VOID_OBJ1 )
#undef _VOID_OBJ1
#define _VOID_OBJn 1
#elif defined( _VOID_OBJ2 )
#undef _VOID_OBJ2
#define _VOID_OBJn 2
#elif defined( _VOID_OBJ3 )
#undef _VOID_OBJ3
#define _VOID_OBJn 3
#elif defined( _VOID_OBJ4 )
#undef _VOID_OBJ4
#define _VOID_OBJn 4
#elif defined( _VOID_OBJ5 )
#undef _VOID_OBJ5
#define _VOID_OBJn 5
#elif defined( _VOID_OBJ6 )
#undef _VOID_OBJ6
#define _VOID_OBJn 6
#elif defined( _VOID_OBJ7 )
#undef _VOID_OBJ7
#define _VOID_OBJn 7
#elif defined( _VOID_OBJ8 )
#undef _VOID_OBJ8
#define _VOID_OBJn 8
#elif defined( _VOID_OBJ9 )
#undef _VOID_OBJ9
#define _VOID_OBJn 9 
#else
#error Attempted to define more than ten objects
#endif //#}

// -------------------------------------------------------
// If the user defines _VOID_NEW_CLASS
// Create a union of the two class structs, xxx_t and xxx_mt
// and call it xxx_ct.  It must also be compatible with xxx_ctt, the tag
// which allows forward definitions in the class headers.

#ifdef  _VOID_NEW_CLASS //#{
#ifndef M  //#{
#define M( var , method , ... )\
        (( (typeof(var._VOIDBIND_T))_VOID_BIND( sizeof(*(var._VOIDBIND)) ) )->\
        method( & var , ## __VA_ARGS__ ))
#endif //#}
extern _VOID_CAT( _VOID_NEW_CLASS , _mt ) _VOID_CAT( _VOID_NEW_CLASS , _mt_ld );
typedef union _VOID_CAT( _VOID_NEW_CLASS, _ctt ) {
    char (*_VOIDBIND)[ _VOID_OBJn ];
    _VOID_CAT( _VOID_NEW_CLASS , _mt ) *_VOIDBIND_T;
    _VOID_CAT( _VOID_NEW_CLASS , _t ) m ;
} _VOID_CAT( _VOID_NEW_CLASS , _ct );

static inline void* (_VOID_CAT( _VOID_OBJ , _VOID_OBJn )) ( void ) {
    return & _VOID_CAT( _VOID_NEW_CLASS, _mt_ld );
}
#undef _VOID_NEW_CLASS
#else // ---------- Otherwise, just bind whatever object was passed in
static inline _VOID_CAT( _VOID_OBJ , _VOID_OBJn ) (void) {
    return (void*) & _VOID_NEW_OBJ ;
}
#undef _VOID_NEW_OBJ
#endif //#}

// End of Macros to define a list of pointers to class method structures
// and to bind data types to method types.

#endif //#}
24
  • 1
    As I said, g++ is often not available on all microcontroller platforms. And the code not only compiles, it runs just fine. class1_ct, is actually defined by the binding macro in voidbind.h, and creates a union which binds class1_t and class1_mt together. class1_ctt is an incomplete type which is the same as class1_ct will be defined as, so that function prototypes can be made before including voidbind.h. Commented Apr 28, 2015 at 4:01
  • 1
    The code is simpler to read. And generally, I don't need to carry around longer names all over the place to distinguish classes, which is exactly what you are doing; For when I write the code, I simply say M( a, init ) and WHATEVER the type of a -- it automatically picks the correct class; so if I change the class type for 'a', the code is all still going to be correct throughout the rest of my program(s). I would have to hand re-edit it, using your technique Note: Being able to bind statically, which is what I want here, doesn't prevent additional mechanisms for polymorphism later. Commented Apr 28, 2015 at 4:13
  • 2
    Ugh. Unreadable as hell. I won't even try to understand it. As @Cornstalks says, no polymorphism means no OOP, so it's still not clear what you are trying to achieve. Commented Apr 28, 2015 at 4:17
  • 1
    No. your objects don't have methods. Your static types have methods associated with them. For OOP you need to associate methods with (run time values of) object themselves. The correct method should be selectable at run time not at compile time. This is called "late binding" and it the single unique feature that characterizes OOP. Commented Apr 28, 2015 at 4:28
  • 1
    That's simply not correct. OOP as historically defined does not always require vtables. Your confusing C++ with the general notion of OOP. They are not the same. There are many other versions of OOP. en.wikipedia.org/wiki/Object-oriented_programming Commented Apr 28, 2015 at 4:42

4 Answers 4

6

In general what you are asking for is C++. the examples you posted are most likely going to be the more efficient or equally efficient using a C++ compiler.

Often on embedded targets you have far outdated versions of gcc that generate bad code for c++ or don't support all the gory c++ details.

You can try to run ${your_arch_prefix}-g++ --nostdlib --nostdinc which will enable c++ syntax in the parser without all the things that waste space. if you want to disable other things you can add -fno-rtti -fno-exceptions with remove runtime type checking and exception support (See this question).

Since the C++ parser is part of the C front-end even though C++ isn't officially supported by your micro controller vendor, this might still be working (sometimes you can also give it a try to compile the vendor specific version yourself and add c++ to the languages in the configure script).

This is usally considered superior to trying to invent your own OOP like macro DSL (domain specific language).

This being said if you don't want to go this path and don't want to use hand-craft vtables (as in your link). The simplest thing to do is have coding conventions. If you don't want polymorphism the code below is sufficient. you can define your struct and functions in a .c file and put the declarations in headers. The function below can be called directly so it's not in a vtable, and the first member is the this pointer in c++. struct impl is the actual data that the object holds not a vtable or similar.

struct impl;
struct impl *make_impl();
// don't use this as it is a reserved keyword in c++
void do_bar(struct impl *myThis, int bar);

If you want polymorphism look at what the kernel does. they explicitly embedd the vtable in the object and use macros to extract them and initialze them.

look at the definition of char device for instance.

and look at how people instanciate this in code and headers. Look at the container_of macro and understand how media_entity_to_video_device casting works. (If this is too little context for you, look at this Book: Linux Device Drivers (LDD3)).

I know your code works and you should be proud to understand what you are doing. But if you show your code to other people, they expect you to either write C or C++. If you are in C and are missing OOP I would try to write the code in a way, that others can grasp easily what you are doing. Using macros to extract function pointers or get a polymorphic member is usually fine, hiding function calls and generate structs in macros is often unreadable and people have to debug your code while running gcc -E to see your creations expanded from the preprocessor to understand what they are actually calling.

Edit

I've had a very quick shot at generating C code from clang++. according to this so question and this one the commands should be:

$ clang++ -std=c++11 -S -emit-llvm -o out main.cc # Worked
$ llc -march=c out 
llc: error: invalid target 'c'.

 $ clang++ --version
 clang version 3.7.0 (trunk 232670)
Target: x86_64-unknown-linux-gnu
Thread model: posix

It seems the clang C backend has been removed (see also these sources resurrecting the C-backend code). That being said you could also have a look at generating a backend for your target plattform, but I think thats definitely over-engineered.

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for the comment on clang, I will look into that ; but I didn't know about it before -- There is no g++ binary in a few of the microcontroller packages I have, although I appreciate your outlining the flags like that. But, I take it I can't get this result with a flag passed to the binary gcc ? I would think that my mistake, mostly, in the opening post is in not commenting and explaining the code's conventions; so I've tried to fix that in the original post. But yes, it's best to use gcc -E to understand how it works.
and have you tried feeding the compiler a .cc or .cpp file? that might trigger the parser to use c++ (without the c++ runtime)
"no g++ binary" The vendor must supply the sources they used to build their binaries (the GPL thing). You can try to build a more complete GCC installation that includes g++ from these sources.
alex: I tried to pass a .cc file with "class one { int x; };" as it's only line to gcc on my PC x86, and yes it worked. So -- cool! I'll try it on some of my microcontroller targets later today and see what happens. :)
@user2133679 about the doesn't make sense to use C++ on embedded. some meta programming techniques involving templates might be interesting, because they allow static polymorphism without wasting any space in a binary for instance. the same goes for function overloading. also interesting is the fact that the compiler isn't forced to generate the complete class if some functions aren't used when the class is a template class.
|
1

For the side question, you can use -std=gnu99 to get C99 with gnu extensions (like anonymous struct and union members within structs and unions).

3 Comments

No good :( The result of deleting the 'm' name in the union on line 122 of voidbind.h, and .m. on line 14 of classtest.c, and lines 44 and 43 of class1.h, and then compiling with gcc -std=gnu99 or gnu11 leads the same error: voidbind.h:122:39: warning: declaration does not declare anything [enabled by default] --- The union itself, after gcc -E, looks like: typedef union class1_ctt { char (*_VOIDBIND)[ 0 ]; class1_mt *_VOIDBIND_T; class1_t ; } class1_ct; _VOID_CAT( _VOID_NEW_CLASS , _t ) ;
Edit malfunctioned; the final _VOID_CAT() is not supposed to be there. sorry.
I know that GCC, and my version is supposed to do anonymous unions, and structs, so I don't understand why with the compiler flags passed, like you show, that I'm still getting "declaration does not declare" warnings, and then proving it's been removed, saying "class1_ct" has no member named 'name'; Is there a simple example of anonymous struct inside a union perhaps unrelated to my solution, which you KNOW at least compiles on gcc 4.6.0 and above ? eg: an example I can look at ?
1

The question mentions -std=c11, so I guess that use of _Generic is OK in this situation.

Since what you appear to be asking for is a way to statically resolve methods from a shared name based on argument type, it makes some sense to look at overloading(/static polymorphism/ad-hoc polymorphism/etc.) as the basis for your system's operation, rather than trying to optimize a pattern generally intended for runtime resolution. _Generic is a static type->value selection operator that is intended specifically for helping with situations like this. It allows you to macro-expand the type-selection code directly into the calling expression and guarantees it will be removed at compile-time, which is exactly what you need.

Since it's an expression operator, _Generic has to list all of the types it's going to operate on in the expression. This means something has to be clustered, which isn't a perfect fit for your OOP strategy. Conventional overloading strategies cluster the function definitions, which would mess up trying to organize methods into classes; however, if you're willing to make an explicit list of all classes in use in your program (i.e. cluster the types instead) it should still be possible to achieve static resolution in a similar way.

e.g. (rough example):

#include <stdio.h>

// shared method table structure for all classes
typedef struct {
    void (* init)( void* );
    char* (* tryme)( void*, char* echo );
} poly_method_table;

// define class1
typedef struct {
    char* name;
    int   one;
} class1;
void class1_init( class1* self ) {
    self->name = "test";
    self->one=5;  
}
const poly_method_table class1_mt = {
    .init = class1_init
};

// define class2
typedef struct {
    int x;
} class2;
char* class2_tryme( class2* self, char* echo ) {
    return echo;
}
const poly_method_table class2_mt = {
    .tryme = class2_tryme
};

// global lookup table
const poly_method_table * table_select[] = {
    &class1_mt,
    &class2_mt,
};
#define M(MSG, THIS, ...) table_select[_Generic((THIS), \
    class1 *: 0, \
    class2 *: 1, \
    default: "error")]->MSG((THIS), ## __VA_ARGS__)


int main( void ) {
    class1 a;
    class2 b;

    M( init, &a );
    printf("a=%s %s\n",a.name, M( tryme, &b, "echo is this" ) );
}

The method operator M produces a constant lookup value into the global table-of-vtables (instead of trying to retrieve the vtable from the object itself). With enough const declarations I would expect a decent optimizer to be able to remove this and go straight to the selected function, since there's no runtime variance in which vtable gets selected.

Since you're already using GNU extensions (i.e. ,## for method calls), you could improve this by using typeof to cast the vtable lookup to a specialized type for each class (instead of having a single vtable class that supports all polymorphic method names), potentially reducing size somewhat and making room for further overloading at the method level.

You could remove the annoying repetition in the definitions of table_select and M with a FOR_EACH macro (it would automatically fill out the table, the middle of the _Generic block, and an enum to build indexes), e.g.:

#define CLASSES class1, class2 //etc.

#define BUILD_ENUM(class) class ## _enum,
#define BUILD_SELECTOR(class) &class ## _mt,
#define SELECT_CLASS(class) class *: class ## _enum,

#define M(MSG, THIS, ...) table_select[_Generic((THIS), \
  FOR_EACH(SELECT_CLASS, CLASSES) \
  default: "error")]->MSG((THIS), ## __VA_ARGS__)

enum { FOR_EACH(BUILD_ENUM, CLASSES) };
const poly_method_table * table_select[] = {
    FOR_EACH(BUILD_SELECTOR, CLASSES)
};

(you can find suitable definitions of FOR_EACH elsewhere on SO)

7 Comments

It's OK to consider -std-C11 solutions, although any GCC solution which doesn't work from all of GCC 3.2 onward is going to be a partial solution based on my OP. I was thinking of making my macro's switch on additional features based on which GCC version is involved in the compile so that the basic binding works on any of GCC 3.2 forward, but where additional features could be enabled if a more powerful version of GCC was available. eg: there is no memory penalty for having a union of an anonymous struct, and a named struct, both -- so compatibility is possible with type safe upgrades.
Hmmm... What version of GCC started allowing FOR_EACH ? as I don't see it in the C11 standard notes; and also when I compile the rough example code, gcc -std=c11 crude.c ; it bombed with "crude.c:39:5 error expected expression before class1_t." which is right in the _Generic macro call. I don't see anything wrong before class1_t... so I'm not sure what's going wrong.
@user2133679 FOR_EACH isn't predefined, but needs to be defined in your code or in an included library (example implementation). With that, the above code should work in GCC 4.9+. (initial version of the second block wasn't tested, just typed into SO - I have corrected it now)
If I install gcc 4.9.2, it's able to compile, but not link. So the compile errors in the first example was a compiler issue ... and unfortunately on my 32/64 bit distro, I can't get your program to link with 4.9.2 -- some kind of mulilib bug... not your fault. It's weird, though, that 4.8.2 -- can't compile it, for it was released in October of 2013, which is >> std=c11 ; So I take it 4.9.x has some kind of bug fix ?
The original OP, if you look at the code, uses a macro to embed the constant which you are computing in _Generic automatically. So, I don't see that you are really improving the system by using _Generic because table select is still a list of typecast pointers, which means the _mt structures have to be passed to the linker -- and can't be optimized out. At least not by gcc 3.2; To avoid the linker, I need a list of instantiated structs that we never take the address of. :) Your idea of a shared method table gave me an idea to try, for the poly table could be a union of class unions...
|
1

If you're willing to forgo any runtime polymorphism, you can completely get rid of your method table objects by replacing them with a _Generic structure simulating a compile-time dispatch table. You can first dispatch on the declared type of an object to choose its static method table, and then dispatch on a dummy type declared to match the method name, to resolve the actual method to call. Basic structure:

#define M(MSG, THIS, ...) _Generic((THIS), \
  class1 *: class1_selector((struct class1_ ## MSG ## _dmy*)0), \
  class2 *: class2_selector((struct class2_ ## MSG ## _dmy*)0), \
  default: "error")(THIS, ## __VA_ARGS__)

(note: there is a reason why I reversed the THIS/MSG operands, explained below)

The method call operator M is built around a centralized list of all the classes in the program. It dispatches on the THIS pointer to select a classX_selector macro to call. It passes the selector a dummy pointer of a type named around the method (cast from zero is fine, we're not going to use it anyway).

#define class1_selector(MSG) _Generic((MSG), \
  struct class1_init_dmy *: class1_init, \
  struct class1_show_dmy *: class1_show, \
  struct class1_getOne_dmy *: class1_getOne, \
  default: "error")

The classX_selector macro expands to a static dispatch table for all of the methods supported by that class. In this case, class1 is defined to support three methods init, show and getOne. The type of the dummy pointer is used to select a method using another type dispatch table. The method is returned, becomes the return value of M's _Generic structure, and is called with object and arguments.

_Generic isn't the only compile-time operator (e.g. the ternary operator should be compile-time as well when given constants), but it has three advantages: firstly, it guarantees the operation will not happen at runtime; secondly, it doesn't double-evaluate your THIS pointer (since the expression used for dispatch is not compiled); and thirdly, since the dummy type expressions are name-based, we don't need to waste effort computing enum IDs for methods, making sure they're consistent across class definitions... just paste the name and the selector works. (Note that you don't even have to declare the dummy types - it's implicit in the use, although doing so doesn't hurt.)

Fundamentally this is really just overloading, but it's oriented towards grouping the method definitions by class instead of by selector name, so there's still some element of OOP to it.

Working example:

#include <stdio.h>

// centralized list of classes
#define CLASSES  class1, class2

// static class dispatch
#define M(MSG, THIS, ...) _Generic((THIS), \
  class1 *: class1_selector((struct class1_ ## MSG ## _dmy*)0), \
  class2 *: class2_selector((struct class2_ ## MSG ## _dmy*)0), \
  default: "error: unknown class")(THIS, ## __VA_ARGS__)


// define class1
typedef struct {
    char* name;
    int   one;
} class1;
void class1_init( class1* self ) {
    self->name = "test";
    self->one=5;  
}
void class1_show(class1 * self) { printf("class1: (%s, %d)\n", self->name, self->one); }
int class1_getOne(class1 * self) { return self->one; }

// class1 static method dispatch table
#define class1_selector(MSG) _Generic((MSG), \
  struct class1_init_dmy *: class1_init, \
  struct class1_show_dmy *: class1_show, \
  struct class1_getOne_dmy *: class1_getOne, \
  default: "error: unknown method")


// define class2
typedef struct {
    int x;
} class2;
void class2_show(class2 * self) { printf("class2: (%d)\n", self->x); }
char* class2_tryme( class2* self, char* echo ) { return echo; }

// class2 static method dispatch table
#define class2_selector(MSG) _Generic((MSG), \
  struct class2_tryme_dmy *: class2_tryme, \
  struct class2_show_dmy *: class2_show, \
  default: "error: unknown method")


int main(void) {
    class1 a;
    class2 b;

    M( init, &a );
    b.x = 13;
    M( show, &a );
    M( show, &b );
}

Because I hate repetition and like excessive metaprogramming, here's a version that uses looping macros to eliminate most of the character overhead involved in defining classes (the block at the top should be hidden away in a different file; cmacros.h is implemented here):

#include <stdio.h>

// !!METAPROGRAMMING BOILERPLATE
#include "cmacros.h"
// static class dispatch
#define M(MSG, ...) _Generic(M_FIRST(__VA_ARGS__), \
  M_REST(M_REST(M_FOR_EACH(M_RE_EXP, \
    (D1, D2, D3) \
    M_ZIP_WITH(MSG_SEL, (CLASSES), M_ENLIST(MSG, M_NARGS(CLASSES))) ) )) \
  ,default: "error: unknown class") \
  (__VA_ARGS__)

#define M_RE_EXP(E) ,M_FIRST E*: _Generic(DUMMY_SEL(M_FIRST E, M_FIRST(M_REST E)), \
  M_CONC2(M, M_REST(M_REST E)) \
  default: "error: unknown method")

#define M_CONC2(L, R) M_CONC2_(L, R)
#define M_CONC2_(L, R) L##R

#define MSG_SEL(CLASS, MSG) ,MSG_SEL_(CLASS, MSG)
#define MSG_SEL_(CLASS, MSG) (CLASS, MSG, LIST_METHODS(CLASS, CLASS ## _methods))

#define DUMMY_SEL(CLASS, MSG) DUMMY_SEL_(CLASS, MSG)
#define DUMMY_SEL_(CLASS, MSG) (struct CLASS##_##MSG##_dmy*)0

#define LIST_METHODS(CLASS, ...) \
  _ZIP_WITH(METHOD_SEL, M_ENLIST(CLASS, M_NARGS(__VA_ARGS__)), (__VA_ARGS__))
#define METHOD_SEL(CLASS, METH) METHOD_SEL_(CLASS, METH)
#define METHOD_SEL_(CLASS, METH) struct CLASS##_##METH##_dmy*: CLASS##_##METH,
// !!END OF BOILERPLATE


// centralized list of classes
#define CLASSES  class1, class2


// define class1
typedef struct {
    char* name;
    int   one;
} class1;
void class1_init( class1* self ) {
    self->name = "test";
    self->one=5;  
}
void class1_show(class1 * self) { printf("class1: (%s, %d)\n", self->name, self->one); }
int class1_getOne(class1 * self) { return self->one; }

#define class1_methods init, show, getOne


// define class2
typedef struct {
    int x;
} class2;
void class2_show(class2 * self) { printf("class2: (%d)\n", self->x); }
char* class2_tryme( class2* self, char* echo ) { return echo; }

#define class2_methods show, tryme


int main(void) {
    class1 a;
    class2 b;

    M( init, &a );
    b.x = 13;
    M( show, &a );
    M( show, &b );
}

Finally, this last version shows the reason for swapping MSG and THIS in the definition of M - it makes it possible to eliminate warnings about unused variadic arguments without relying on a GCC extension. (Besides, who says you need to be controlled by C++'s obj.method convention?)

N.B. there is a possible downside to this strategy (who would have thought) - the macro step pastes the full method table selection for every class in at each method call site. There's no runtime code bloat because _Generic removes it all again, but it will probably slow compilation down, or most likely run out of compiler memory, if you have hundreds of classes and methods! Overloading would be much more efficient in this respect.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.