vendredi 13 mars 2015

Convert a recursive variadic template function into iterative


Say I have the following stuct



#include <functional>

template <typename ...T>
struct Unpack;

// specialization case for float
template <typename ...Tail>
struct Unpack<float, Tail...>
{
static void unpack(std::function<void(float, Tail...)> f, uint8_t *dataOffset)
{
float val;
memcpy(&val, dataOffset, sizeof(float));

auto g = [&](Tail&& ...args)
{
f(val, std::forward<Tail>(args)...);
};

Unpack<Tail...>::unpack(std::function<void(Tail...)>{g}, dataOffset + sizeof(float));
}
};

// base recursive case
template <typename Head, typename ... Tail>
struct Unpack<Head, Tail...>
{
static void unpack(std::function<void(Head, Tail...)> f, uint8_t *dataOffset)
{
Head val;
memcpy(&val, dataOffset, sizeof(Head));

auto g = [&](Tail&& ...args)
{
f(val, std::forward<Tail>(args)...);
};

Unpack<Tail...>::unpack(std::function<void(Tail...)>{g}, dataOffset + sizeof(Head));
}
};

// end of recursion
template <>
struct Unpack<>
{
static void unpack(std::function<void()> f, uint8_t *)
{
f(); // call the function
}
};


All it does is takes a std::function and a byte-array, and chunks off the byte-array, applying those chunks as function's arguments, recursively, until all arguments are applied, and then call the function.


The issue I'm having with this is that it generates quite a lot of templates. This is especially noticeable when used extensively in debug mode -- it causes the binary to grow very fast.


Given the following use case



#include <iostream>
#include <string.h>

using namespace std;


void foo1(uint8_t a, int8_t b, uint16_t c, int16_t d, uint32_t e, int32_t f, uint64_t g, int64_t h, float i, double j)
{
cout << a << "; " << b << "; " << c << "; " << d << "; " << e << "; " << f << "; " << g << "; " << h << "; " << i << "; " << j << endl;
}

void foo2(uint8_t a, int8_t b, uint16_t c, int16_t d, uint32_t e, int32_t f, int64_t g, uint64_t h, float i, double j)
{
cout << a << "; " << b << "; " << c << "; " << d << "; " << e << "; " << f << "; " << g << "; " << h << "; " << i << "; " << j << endl;
}

int main()
{
uint8_t *buff = new uint8_t[512];
uint8_t *offset = buff;

uint8_t a = 1;
int8_t b = 2;
uint16_t c = 3;
int16_t d = 4;
uint32_t e = 5;
int32_t f = 6;
uint64_t g = 7;
int64_t h = 8;
float i = 9.123456789;
double j = 10.123456789;

memcpy(offset, &a, sizeof(a));
offset += sizeof(a);
memcpy(offset, &b, sizeof(b));
offset += sizeof(b);
memcpy(offset, &c, sizeof(c));
offset += sizeof(c);
memcpy(offset, &d, sizeof(d));
offset += sizeof(d);
memcpy(offset, &e, sizeof(e));
offset += sizeof(e);
memcpy(offset, &f, sizeof(f));
offset += sizeof(f);
memcpy(offset, &g, sizeof(g));
offset += sizeof(g);
memcpy(offset, &h, sizeof(h));
offset += sizeof(h);
memcpy(offset, &i, sizeof(i));
offset += sizeof(i);
memcpy(offset, &j, sizeof(j));

std::function<void (uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, float, double)> ffoo1 = foo1;
Unpack<uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, float, double>::unpack(ffoo1, buff);

// uint64_t and in64_t are switched
//std::function<void (uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, int64_t, uint64_t, float, double)> ffoo2 = foo2;
//Unpack<uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, int64_t, uint64_t, float, double>::unpack(ffoo2, buff);

return 0;
}


the debug binary I get with the two lines being commented is 264.4 KiB, but when I uncomment the two lines it becomes 447.7 KiB, 70% bigger than the original.


Same with release mode: 37.5 KiB vs. 59.0 KiB, 60% bigger than the original.


It makes sense to replace the recursion with iteration, something like an initializer list applied to the variadic Unpack<...>:unpack(), so that C++ would generate only a template per a type.


The code above compiles just fine, if you want to play with it a little.




Aucun commentaire:

Enregistrer un commentaire