Last active
July 13, 2022 00:29
-
-
Save deplinenoise/6297411 to your computer and use it in GitHub Desktop.
You can pass along an array of info with varargs using C++ variadic templates. It generates really tight code; the only remaining overhead at runtime is the static arrays of type information (they end up in the `.rodata` segment). In this example I'm passing in an integer describing each arg, but you could just as well pass in function pointers …
This file contains 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
// Type-safe varargs with C++11 variadic templates. | |
// | |
// Andreas Fredriksson <deplinenoise at gmail dott com> | |
// | |
// This code is in the public domain. | |
#include <stdio.h> | |
#include <stdarg.h> | |
enum { | |
kTypeInt = 1, | |
kTypeFloat = 2, | |
kTypeString = 3, | |
kTypeVec3Ptr = 4, | |
}; | |
struct Vec3 { float x, y, z; }; | |
template <typename T> | |
struct TypeId | |
{ | |
private: | |
// Generate a compile error for types not explicitly supported. | |
// As a bonus this triggers compile time errors for non-POD data being passed | |
// through ellipsis rather than slient undefined behavior. | |
enum { Value = -1 }; | |
}; | |
template <> struct TypeId<int> { enum { Value = kTypeInt }; }; | |
template <> struct TypeId<float> { enum { Value = kTypeFloat }; }; | |
template <> struct TypeId<const char*> { enum { Value = kTypeString }; }; | |
template <> struct TypeId<Vec3*> { enum { Value = kTypeVec3Ptr }; }; | |
template <> struct TypeId<const Vec3*> { enum { Value = kTypeVec3Ptr }; }; | |
template <typename... Args> | |
struct TypeIdArray | |
{ | |
enum { kCount = sizeof...(Args) }; | |
static int kValues[sizeof...(Args)]; | |
}; | |
template <typename... Args> | |
int TypeIdArray<Args...>::kValues[sizeof...(Args)] = { TypeId<Args>::Value... }; | |
template <typename T, typename... Args> | |
TypeIdArray<T, Args...> TypeIdArrayHelper(T, Args...); | |
void DoThing(size_t arg_count, const int arg_types[], ...) | |
{ | |
printf("Called with %d args\n", (int) arg_count); | |
va_list args; | |
va_start(args, arg_types); | |
for (size_t i = 0; i < arg_count; ++i) { | |
switch (arg_types[i]) { | |
case kTypeInt: printf("An integer: %d\n", va_arg(args, int)); break; | |
case kTypeFloat: printf("A float: %f\n", va_arg(args, double)); break; // vararg floats go as double | |
case kTypeString: printf("A string: %s\n", va_arg(args, const char*)); break; | |
case kTypeVec3Ptr: | |
{ | |
const Vec3* v = va_arg(args, const Vec3*); | |
printf("A vec3 ptr: %f %f %f\n", v->x, v->y, v->z); | |
break; | |
} | |
default: printf("Update this switch!"); | |
} | |
} | |
va_end(args); | |
} | |
#define DO_THING(...) \ | |
DoThing(\ | |
decltype(TypeIdArrayHelper(__VA_ARGS__))::kCount, \ | |
decltype(TypeIdArrayHelper(__VA_ARGS__))::kValues, \ | |
__VA_ARGS__) | |
int main() | |
{ | |
DO_THING(1, 2.1f, 1, "foo", "bar"); | |
DO_THING(1); | |
DO_THING("foo", 17); | |
// DO_THING(1u); -- compile-time error, unsigned int not allowed | |
// DO_THING(false); -- compile-time error, bool not allowed | |
Vec3 bar = { -1.0f, 6.0f, 0.0f }; | |
DO_THING("here we go", &bar); | |
// DO_THING("here we go", bar); -- compile-time error | |
int i = 1; | |
DO_THING(++i); | |
printf("i = %d\n", i); // prints 2 - no multiple evaluation | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There is no need for macro : https://gist.github.com/Aszarsha/6309106. Gcc 4.8.1 seem to inline away the variadic function, hence no overhead either. __forceinline might do the job in vc++ if it's not inlined by default (haven't tried).
Bonus : better error message. :-)