Last active
August 11, 2019 12:38
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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