TLTR: I would like to map some arrays from a template container to the arguments of a function according to a specific order defined by indexes stored in a list of variadic templates (I can't think of a simpler way to define the problem).
Arrays are stored using void*
but type safety between the arrays and the function parameters are guaranteed by a helper class. The same helper class has to expand the given parameter packs, fetch the appropriate arrays, bind them to the function pointer and call the function. This is where I am stuck.
detail: I apologize in advance for the lengthy question and posting code which does not compile but I tried to be as concise as possible.
The problem consist in mapping the proper members of a container to a functor object. The container has a list of arrays defined by a typelist, whose implementation is similar to this one.
For simplicity, we assume the typelist helper objects TLAlg::length<TL>
and TLAlg::TypeAt
are defined and allow users to access to the length of a typelist and the Nth type respectively.
A container class allocates an array for each type in the typelist (called field) and stores an opaque pointer to these buffers. A typesafe getter is implemented to access a particular field index. Its implementation is as follow:
// container class, stores an array for each type in the typelist
template<class TL>
class Volume {
public:
// list of opaque buffers
void *opaque_buffers[TLAlg::length<TL>::value];
template<int size>
Volume(const size_t (&dim)[size]){
// each opaque_buffers is initialized here
...
}
// getters are using the field index for type-safety
template <int index> typename
TLAlg::TypeAt<TL, index>::Result &
get(const std::initializer_list<size_t> &position);
};
We want to implement a Functor
object which will apply a given function on the volume using a particular subset of the typelist. Instead of manipulating the arrays directly, the user gives a list of indexes of the fields he wants to access and a pointer on the function to apply. The functor object is responsible to set the correct arguments.
To increase type safety, we separate those in two lists: read only and read/write (read const
and not const
). The given function prototype must agree with the definition of the functor object: the code only compiles if the given function pointer matches the argument definition exactly, so we don't need to worry about mismatching types. The implementation of the functor is:
template<typename TL, class T1, class T2> struct Functor{};
template< typename TL,
template<size_t...> class T1, size_t... A1, // read only arguments
template<size_t...> class T2, size_t... A2 // read-write arguments
>
struct Functor< TL, T1<A1...>, T2<A2...> >{
// type of the function pointer
typedef void (*Type)(const typename TLAlg::TypeAt<TL, A1>::Result* ... ,
typename TLAlg::TypeAt<TL, A2>::Result* ...);
Functor(Volume<TL> &v, Type f): f(f){
// At this point we have everything we need: the volume, the function
// and the list of arguments, but how to combine them all?
// maybe some magic here to map the opaque pointers to the arguments?
}
void operator()(){
// or maybe here?
}
}
As you can see, the functor doesn't do anything at the moment, because I don't know how to map the two parameter packs to the container arrays and bind them to the function pointer...
For clarity, here is an example of usage for the functor class:
// main Typelist
typedef Typelist<float, Typelist<double, Typelist<int, NullType>>> Pixel;
// function we want to apply: reads first and last field of the list and updates second
void foo(const float *f1,
const int *f3,
double *f2){}
// helper class to store the parameter packs
template<size_t ... T> struct Pack {};
int main(){
// volume declaration
Volume<Pixel> volume((size[]){1024,1024});
// delare typesafe functor
Functor<Pixel, // typelist
Pack<0,2>, // list of read-only fields
Pack<1> // list of read-write fields
> apply_foo(volume, foo);
apply_foo(); // <- this does nothing at the moment
}
I tried playing with std::forward
and std::bind
for a long time, but I can't get the right solution yet. Substituting the typelist for std::tuple
might be considered but it is preferable to keep the current definition.
This code might look weird and unnecessarily complicated, but it is a very simplified version of a massive framework where using these classes make sense.
Any help would be highly appreciated.
Clarifications for Yakk's answer:
I do need a typelist because I am doing more magic in it, for example each element of the list can be a tuple instead of a single type to associate a name. This allows neat code like:
typedef MakeTypeList((float, p),
(float, bnd),
(float, gosa)) HimenoFields;
// I can now use aliases, and the declaration order does not matter in the code.
// here is an access to the second field:
volume.get<HimenoFields::bnd>({0,0,0});
You can imagine how this combines very well to the kind of operations I want to implement with the functors.
Second, I understand why you got confused by the getter. As I said initially, this is a very simplified version of the code, despite the very long question. In the real program, the volumes are multi-dimensional and either get flattened in a single array or allocated in a multi-dimensional array, which is why the getter requires complete coordinates. There are several implementations of these getters with different parameters.
Finally, the Functor does not need to know which element to apply the function to, because it is itself controlling the iteration space and applying a pre-defined skeleton (ie stencil, wavefront...). Again i omitted that for the sake of simplicity.