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.
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;
}
Some observations.
1) if you want a compile time execution, you have to use constexpr
.
So your compile_time_for_each()
must be defined constexpr
if you want that can be executed compile time
template <typename data_t, typename... type_list_t>
constexpr void compile_time_for_each (data_t const & data, type_list_t &... objects)
{ /* some potentially compile time code */ }
2) a constexpr
function can be executed both compile time and run time; if you want impose that is executed compile-time... maybe there are other ways but the simplest mode come in my mind is return a value from the function
template <typename data_t, typename... type_list_t>
constexpr int compile_time_for_each (data_t const & data, type_list_t &... objects)
{
/* some potentially compile time code */
return 1;
}
and use the returned value to initialize a constexpr
variable
constexpr auto x = compile_time_for_each(data, functor_1, functor_2);
3) for a compile time execution, you needs compile-time values, so define data
as constexpr
constexpr int data = 42;
or its use prevents the compile time execution.
4) there is no needs of recursion: you tagged C++17 so you can use template folding; by example
template <typename D, typename ... Fs>
constexpr int compile_time_for_each (D const & data, Fs const & ... funcs)
{
(std::apply(funcs, data), ...);
return 0;
}
5) iostream input/output code isn't compatible with compile-time execution; so the following lambdas
auto functor_1 = [] (int data) {std::cout << data;};
auto functor_2 = [] (int data) {data++; std::cout << data;};
can't be executed compile-time.
The following is a C++17 example
#include <tuple>
template <typename D, typename ... Fs>
constexpr int ct_for_each (D const & data, Fs const & ... funcs)
{
(std::apply(funcs, data), ...);
return 0;
}
int main ()
{
constexpr int data = 42;
auto constexpr functor_1 = [] (int data) { /* some_code_1 */ };
auto constexpr functor_2 = [] (int data) { /* some_code_2 */ };
// compile time error if you define functor_1 as follows
// auto constexpr functor_1 = [] (int data) { std::cout << data << std::endl; };
constexpr auto x = ct_for_each(std::make_tuple(data), functor_1, functor_2);
(void)x; // to avoid "unused variable 'x'" warning
}
来源:https://stackoverflow.com/questions/57180174/compiletime-for-each-with-custom-functions