2

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.

Thibaut
  • 2,400
  • 1
  • 16
  • 28
  • Correct me if I'm wrong but I'm under the impression that `Functor, Pack<1>>::Type` is not quite similar to the signature of `foo`. Improvising `std::tuple` as a type-list, you can see [what GCC has to say on the topic](http://coliru.stacked-crooked.com/view?id=d1ce87766f03b88b3494d6e77dae58fc-50d9cfc8a1d350e7409e81e87c2653ba). – Luc Danton Apr 06 '13 at 05:58
  • Well spotted, my bad. I have some more code to re-shuffle the arguments and I was a bit too quick in my copy-paste. The const are packed together in the example i gave. I edited `foo` accordingly. – Thibaut Apr 06 '13 at 18:39

1 Answers1

1

First, I would rewrite your type_list:

template<typename... Ts>
struct type_list {};

with variardic types instead of your 18 argument hack. Writing type_at<n>::type and index_of<T>::value isn't then hard. Mapping between these and your pair-based TypeList isn't hard either.

template<typename list>
struct make_TypeList;

template<typename T0, typename T1, typename...Ts>
struct make_TypeList<type_list<T0, T1, Ts...>> {
  typedef typename make_Typelist< type_list<T1, Ts...> >::type tail;
  typedef TypeList< T0, tail > type;
};
template<typename T0>
struct make_TypeList<type_list<T0>> {
  typedef TypeList< T0, NullType > type;
};
template<>
struct make_TypeList<type_list<>> {
  typedef NullType type;
};

if you really need it. There are reasons to work with non-type-lists, but you aren't demonstrating any.

Building up a collection of compile-time type indexes is a bit tricky, but if you pass in an upper bound, you can do it. The goal is to create a sequence:

template<size_t... s>
struct seq {};

If you are getting these indexes at compile time, this is easier. Once you have this sequence, and you have type_at, you can write a calling function sort of like this:

template<size_t... s, typename... Ts>
void evaluate( seq<s...> unused, void(*func)(Ts*... ts) ) {
  func( &get_at<s>()... );
}

where we unpack the sequence directly into the function call. As it happens, often the sequence in question is merely 0,1,2,3,4,...,n-1, which can be easily generated:

template<size_t max, size_t... s>
struct make_seq:make_seq<max-1, max-1, s...> {};
template<size_t... s>
struct make_seq<0, s...> {
  typedef seq<s...> type;
};

To be clear: operator() calls a helper function after doing a make_seq<sizeof...(Ts)>::type(), passing that into the helper function, which then calls func( &get_at<s>(/*n maybe?*/)... ), and bob is your uncle

One thing that confuses me is this:

// 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);

I'm not sure why const std::initializer_list<size_t> &position is needed, or at the least why you don't have:

template <int index> typename
TLAlg::TypeAt<TL, index>::Result &
get_at(size_t n);

which makes me think that your operator() in your functor is missing "which index do I apply this functor to", if your Volume is an array of multiple types.

But I strongly suspect the "make a pack of indexes, call a helper function so you can get at the guts, then expand the pack in the function call" is the trick you are missing.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks a lot for the elaborate answer. I edited my post to clarify some points you mentioned. I'll try it out today and accept your answer if i can manage to implement it ;) – Thibaut Apr 02 '13 at 10:59
  • @Thibaut `type_list< int, type_list< bool, double >, type_list< foo >, std::string >` allows for fully structured lists-of-lists, just like `TypeList`. The only difference is that `TypeList` is a tree-based type structure (like lisp "lists"), while `type_list` is a linear based type structure, and that C++ has a bunch of support for "iterating" over and "unpacking" linear based type structures. On the other hand, if your type lists approach 1000 in length, recursion limits in C++ template meta programming mean that balanced binary tree based type collections end up being needed. – Yakk - Adam Nevraumont Apr 02 '13 at 14:08
  • Right, i think i got your point. But the indexes for the fields are stored in a variadic template, except mine is called `Pack` instead of `seq`, so I can expand it easily. The Typelist is just here for type safety. Are you saying that i should replace the tree typelist with a flat list in order to "align" the type list with the index list? – Thibaut Apr 02 '13 at 14:20
  • @thibaut I'm saying the flat `type_list` is far easier to manipulate in general in C++11 than tree-based lists are, with lots of built-in support -- in C++03, tree-based lists where about the only way to handle arbitrary length lists. – Yakk - Adam Nevraumont Apr 02 '13 at 14:24