Search code examples
c++c++17variadic-templatestemplate-meta-programming

Compiletime for each with custom functions


Abstract:

Imagine a problem of the following form: One has to invoke multiple specific member functions with the same parameters on a list of functors. That makes a good problem to solve with an interface (runtime_interface, in other words a requirement of functions that those functors have to implement). The Problem I would like to discuss is the case where the list of functors is known at compile time, but might be subject to change during the further development process. Because in this case if implemented like that one is paying the runtime overhead even though all the functions to be called are known at compile time.

General Question:

What are ways of solving Problems like the given one that come with no or just a small runtime overhead. without giving up the modularized structure. I think what is really intresting about this is that its just

My approach:

template <class data_t, class... type_list_t>
struct compile_time_for_each_ref_impl;

template <class data_t, class first_t, class... type_list_t>
struct compile_time_for_each_ref_impl<data_t, first_t, type_list_t...> {
    static void eval(const data_t& data, first_t& object, type_list_t... object_list)
    {
        std::apply(object, data);

        compile_time_for_each_ref_impl<data_t, type_list_t...>::eval(data, object_list...);
    }
};
template <class data_t>
struct compile_time_for_each_ref_impl<data_t> {
    static void eval(const data_t& data) {}
};

template <class data_t, class... type_list_t>
void compile_time_for_each(const data_t& data, type_list_t&... objects)
{
    compile_time_for_each_ref_impl<data_t, type_list_t...>::eval(data, objects...);
}

template <class data_t, class... type_list_t>
void compile_time_for_each(const data_t& data, std::tuple<type_list_t...>& objects)
{
    std::apply(
        [&data] (type_list_t... params) {
            compile_time_for_each_ref_impl<data_t, type_list_t...>::eval(data, params...);
        },
        objects);
}

What I am able to:


int data = 42

auto functor_1 = [] (int data) {std::cout << data;};
auto functor_2 = [] (int data) {data++; std::cout << data;};

compile_time_for_each(std::make_tuple(data), functor1, functor2);

What the code i would like to write looks like::

struct functor1{
    void method1(int);

    int method2(double);
};


struct functor1{
    void method1(int);

    int method2(double);
};

template <class... functors_t>
struct main_mod{
    std::tuple<functors_t...> functors;

    void method1(int some_data){
        compile_time_for_each<method1, functors_t...>(some_data,functors);
    }

    void method2(int some_data){
        compile_time_for_each<method2, functors_t...>(some_data,functors);
    }
};

The problem with my approach:

I dont see a way to pass the name of the function that is supposed to be called on the functor to the compile_time_for_each call. What i could do is to change the hardcoded function name (the example implementation takes the operator() because it makes the code simpler the code but one could hardcode any funtion name) so i would end up with one compile_time_for_each function for every function name that i would like to use.

One Solution(that I dont like to much):

A valid solution would be to make that whole thing a macro and set the actual name of the function in the macro.

At the end for me it is not really about the overhead but not beeing able to express theese things properly.

My actual implementation draft:

It incorporates @Aconcagua's idea of the resolver and the usage of fold expressions that @max66 suggested aswell. In this state I have not done any optimizations but I like the Interface and that was my main goal. Even though I think it should be doable without any overhead. If you are seeing this and have any ideas or suggestions hit me up.

https://godbolt.org/z/LfmSSb


Solution

  • Using a lambda I managed to get pretty close to what you intend, even though I failed to provide an exact match:

    template<typename Executor, typename Data, typename ... Functors>
    void for_each(Executor executor, Data const& data, Functors ... functors)
    {
        // C++17 fold expression:
        (executor(functors, data), ...);
    }
    
    class C0
    {
    public:
        void test0(int) const { std::cout << "00" << std::endl; }
        void test1(int) const { std::cout << "01" << std::endl; }
    };
    class C1
    {
    public:
        void test0(int) const { std::cout << "10" << std::endl; }
        void test1(int) const { std::cout << "11" << std::endl; }
    };
    
    int main()
    {
        for_each([](auto const& c, int data) { c.test0(data); }, 7, C0(), C1());
        for_each([](auto const& c, int data) { c.test1(data); }, 7, C0(), C1());
        return 0;
    }