-
-
Save prapin/5c90b8246e86a2c5d0ede0928cfbdb1d to your computer and use it in GitHub Desktop.
A simpler version that decreases the overhead of the variadic call.
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
// Type-safe varargs with C++11 variadic templates. | |
// | |
// Andreas Fredriksson <deplinenoise at gmail dott com> | |
// minor changes from: | |
// Thomas Hume <thomas dot hume at labri dot fr> | |
// modified by Patrick Rapin | |
// | |
// This code is in the public domain. | |
#include <stdio.h> | |
#include <stdarg.h> | |
#include <stdint.h> | |
#include <string.h> | |
struct Vec3 { float x, y, z; }; | |
namespace VarArg { | |
enum : uint8_t { | |
kTypeInt = 1, | |
kTypeFloat = 2, | |
kTypeString = 3, | |
kTypeVec3Ptr = 4, | |
}; | |
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 silent undefined behavior. | |
enum { Value = 0 }; | |
}; | |
template <> struct TypeId<int> { enum { Value = kTypeInt }; }; | |
template <> struct TypeId<float> { enum { Value = kTypeFloat }; }; | |
template <size_t N> struct TypeId<char[N]> { enum { Value = kTypeString }; }; | |
template <> struct TypeId<char*> { enum { Value = kTypeString }; }; | |
template <> struct TypeId<Vec3*> { enum { Value = kTypeVec3Ptr }; }; | |
template <class T> struct TypeId<const T*> { enum { Value = TypeId<T*>::Value }; }; | |
} | |
void DoThing(const uint8_t* arg_types, ...) | |
{ | |
using namespace VarArg; | |
printf("Called with %zu args\n", strlen((char*)arg_types)); | |
va_list args; | |
va_start(args, arg_types); | |
for (const uint8_t* ptype = arg_types; *ptype; ptype++) { | |
switch (*ptype) { | |
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); | |
} | |
template< typename... Args > | |
inline void TypedVariadicCall(const Args& ... args) | |
{ | |
static const uint8_t types[sizeof...(Args)+1] = { VarArg::TypeId<Args>::Value..., 0 }; | |
DoThing(types, args...); | |
} | |
int main() | |
{ | |
TypedVariadicCall(1, 2.1f, 1, "foo", "bar"); | |
TypedVariadicCall(1); | |
TypedVariadicCall("foo", 17); | |
// TypedVariadicCall(1u); //-- compile-time error, unsigned int not allowed | |
// TypedVariadicCall(false); //-- compile-time error, bool not allowed | |
Vec3 bar = { -1.0f, 6.0f, 0.0f }; | |
TypedVariadicCall("here we go", &bar); | |
// TypedVariadicCall("here we go", bar); //-- compile-time error | |
int i = 1; | |
TypedVariadicCall(++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
I have been using several variations of
typesafe_varargs.cpp
in our company framework.I am posting here a stripped version that performs exactly the same operations as the original.
The main differences are:
VarArg
to avoid pollutionTypeIdHelper
andTypeIdArray
uint8_t
terminated by 0, which takes 4 times less space that withint
. It is like a literal string (but with a different type to avoid confusion).strlen
to compute if needed (most of the time, we just loop up to the terminating NUL)