Skip to content

Instantly share code, notes, and snippets.

@deplinenoise
Last active July 13, 2022 00:29
Show Gist options
  • Save deplinenoise/6297411 to your computer and use it in GitHub Desktop.
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 …
// 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
}
@Aszarsha
Copy link

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. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment