Skip to content

Instantly share code, notes, and snippets.

@ClysmiC
Last active August 11, 2019 12:38
Show Gist options
  • Save ClysmiC/d9a13ef95642c33b163872f73113c96f to your computer and use it in GitHub Desktop.
Save ClysmiC/d9a13ef95642c33b163872f73113c96f to your computer and use it in GitHub Desktop.
A hacky attempt to implement interfaces without using member functions or inheritance. Inspired by Go, but I've never actually used Go so I could be totally wrong about how they do it. Not intended to be useful... more a proof of concept for a way that a compiler could implement this under the hood
#include <cstdio>
#include <stdarg.h>
#define BeginInterface(ifaceName) struct ifaceName##ITable {
#define BeginListFn static constexpr int s_iLineFnBase = __LINE__;
#define AddFn(returnType, funcName, ...) typedef returnType ( *Pfn##funcName )(void* __VA_ARGS__ ); static constexpr int s_iFn##funcName = __LINE__ - 1 - s_iLineFnBase;
#define BeginListImpl static constexpr int s_iLineImplBase = __LINE__; static constexpr int s_cFn = s_iLineImplBase - s_iLineFnBase - 1;
#define AddImpl(structName) static constexpr int s_iImpl##structName = __LINE__ - 1 - s_iLineImplBase;
#define EndInterface(ifaceName) static constexpr int s_cImpl = __LINE__ - 1 - s_iLineImplBase;\
static constexpr int s_cFnPtrs = s_cImpl * s_cFn;\
void * fnPtrs[s_cFnPtrs];\
ifaceName##ITable(void * ptr, ...) {\
va_list args;\
va_start(args, ptr);\
int i = 0;\
for (void * arg = ptr; arg; arg = va_arg(args, void*))\
fnPtrs[i++] = arg;\
va_end(args);\
}};\
struct ifaceName {\
int iInstance;\
void * ptr;\
ifaceName() : iInstance(-1), ptr(nullptr) {}\
ifaceName(int iInstance, void * ptr) : iInstance(iInstance), ptr(ptr) {}\
};
#define InterfaceInit(ifaceName, structName, structPtr) ifaceName(ifaceName##ITable::s_iImpl##structName, (structPtr))
#define CallInterface(ifaceName, funcName, ifaceInstance, ...) ((ifaceName##ITable::Pfn##funcName)(g_##ifaceName##ITable.fnPtrs[ifaceName##ITable::s_iFn##funcName * ifaceName##ITable::s_cImpl + ifaceInstance.iInstance]))(ifaceInstance.ptr, __VA_ARGS__)
// To create an interface, call the macros as below. They MUST be called in the right order and they MUST not have any blank lines between them.
// After defining all of the functions needed to implement the interface for each implementing struct, you must initialize a global variable
// containing the ITable. Ideally this would be done automatically but my macro game isn't strong enough for that :( See g_IShape3DITable below.
// The name must match the pattern exactly! "g_<interface_name>ITable"
// NOTE: If you put args after the function name in AddFn you must put an extra comma after the function name. It's the only way I could get the macro to work :(
BeginInterface(IShape3D)
BeginListFn
AddFn(float, volume)
AddFn(bool, moreFacesThan,, int) // Extra comma, see above
AddFn(void, printName)
BeginListImpl
AddImpl(Rect3D)
AddImpl(Sphere)
AddImpl(Cylinder)
EndInterface(IShape3D)
struct Rect3D
{
int length = 1;
int width = 1;
int height = 1;
};
float volume(Rect3D * rect)
{
return rect->length * rect->width * rect->height;
}
bool moreFacesThan(Rect3D * rect, int faces)
{
return 6 > faces;
}
void printName(Rect3D * rect)
{
printf("Rect3D\n");
}
struct Sphere
{
int radius = 1;
};
float volume(Sphere * sphere)
{
return 4.0f / 3.0f * 3.14159f * sphere->radius * sphere->radius * sphere->radius;
}
bool moreFacesThan(Sphere * sphere, int faces)
{
return 1 > faces;
}
void printName(Sphere * sphere)
{
printf("Sphere\n");
}
struct Cylinder
{
int radius = 1;
int height = 1;
};
float volume(Cylinder * cylinder)
{
return 3.14159f * cylinder->radius * cylinder->radius * cylinder->height;
}
bool moreFacesThan(Cylinder * cube, int faces)
{
return 3 > faces;
}
void printName(Cylinder * cylinder)
{
printf("Cylinder\n");
}
// The interface usage macros depend on this global being defined/initialized with the right pointers in the right order.
// Not sure how I could realistically initialize this with a macro... but it is easily something that a compiler could generate
// and use under the hood.
IShape3DITable g_IShape3DITable(
(float(*)(Rect3D*))volume,
(float(*)(Sphere*))volume,
(float(*)(Cylinder*))volume,
(bool(*)(Rect3D*, int))moreFacesThan,
(bool(*)(Sphere*, int))moreFacesThan,
(bool(*)(Cylinder*, int))moreFacesThan,
(void(*)(Rect3D*))printName,
(void(*)(Sphere*))printName,
(void(*)(Cylinder*))printName
);
int main()
{
Rect3D rect;
rect.width = 4;
rect.length = 2;
rect.height = 1;
Sphere sphere;
sphere.radius = 2;
Cylinder cylinder;
cylinder.radius = 1;
cylinder.height = 4;
IShape3D shapes[3];
shapes[0] = InterfaceInit(IShape3D, Rect3D, &rect);
shapes[1] = InterfaceInit(IShape3D, Sphere, &sphere);
shapes[2] = InterfaceInit(IShape3D, Cylinder, &cylinder);
for (int i = 0; i < 3; i++)
{
CallInterface(IShape3D, printName, shapes[i]);
printf("Volume: %f\n", CallInterface(IShape3D, volume, shapes[i]));
printf("More than 3 faces?: %s\n", CallInterface(IShape3D, moreFacesThan, shapes[i], 3) ? "yes" : "no");
printf("\n");
}
// Output
//
// Rect3D
// Volume: 8.000000
// More than 3 faces?: yes
//
// Sphere
// Volume: 33.510296
// More than 3 faces?: no
//
// Cylinder
// Volume; 12.566360
// More than 3 faces?: no
getchar();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment