C++17: pass multiple callable objects together with their parameters

97 Views Asked by At

I would like to write a function (C++17 if that matters) that accepts multiple callable objects together with their arguments and executes them in a loop. I would also like to avoid any intermediate copying of data.

Example:

Assume we have three functions f0, f1 and f2 that expect different number of input parameters

void f0() {
 // Implementation of f0
}
void f1(int a) { 
 // Implementation of f1
}
void f2(int a, int b) {
 // Implementation of f2
}

I'm looking for a way to pass all functions above together with sample input arguments into class or another function (say Execute). A sample interface would look like following:

int main() {
  // If Execute is a class
  Execute exec({f0}, {f1, 4}, {f2, 1, 2});

  // The result of the command above is a sequence of calls
  // 1. f0()
  // 2. f1(4)
  // 3. f2(1, 2)
}

I would like to avoid any intermediate copying of data.

So my question how would one achieve that?

3

There are 3 best solutions below

1
n. m. could be an AI On BEST ANSWER

For completeness, here's a zero effort solution.

template <typename... T> 
void execute (T&&... t)
{
    (t(),...);
}

int main()
{
    int a = 42, b = 0;
    // this
    execute([&](){f0();}, [&](){f1(a);}, [&](){f2(a,b);});
    // or this
    execute(std::bind(f0), std::bind(f1, a), std::bind(f2, a, b));
}
0
Lluís Alemany-Puig On

Here's a working solution that implements a templated class using std::tuple that imitates what you suggested.

#include <iostream>
#include <utility>
#include <vector>
#include <tuple>

void f0() {
    std::cout << "f0\n";
}

void f1(int a) {
    std::cout << "f1: " << a << '\n';
}

void f2(int a, int b) {
    std::cout << "f2: " << a + b << '\n';
}

void f3(const std::string a, int b) {
    std::cout << "f3: " << a << " ** " << b << '\n';
}

template <typename T1, typename... Ts>
std::tuple<Ts...> unshift_tuple(const std::tuple<T1, Ts...>& tuple)
noexcept
{
    return std::apply(
        [](auto&&, const auto&... args) {
            return std::tie(args...);
        },
        tuple
    );
}

template <typename Callable, typename... Params>
void call(const std::tuple<Callable, Params...>& t) noexcept {
    const auto f = std::get<0>(t); 
    const auto args = unshift_tuple(t);
    std::apply(f, args);
}

template <typename... Params>
class Execute {
public:
    Execute(std::tuple<Params...>&& t) : m_fs(t) { }
    
    void execute() noexcept {
        iterate_over_functions<0>(m_fs);
    }
    
private:
    template <std::size_t I, typename... Ts>
    void iterate_over_functions(const std::tuple<Ts...>& t) noexcept {
        if constexpr (I < sizeof...(Ts)) {
            call(std::get<I>(t));
            iterate_over_functions<I + 1>(t);
        }
    }
    
private:
    std::tuple<Params...> m_fs;
};

int main() {
    Execute f(
        std::make_tuple(
            std::make_tuple(f0),
            std::make_tuple(f1, 1),
            std::make_tuple(f2, 1, 2),
            std::make_tuple(f3, "Stack Overflow!", 2)
        )
    );
    
    f.execute();
}

Note: I needed to remember how to unshift a tuple and call a function using as arguments the values in a std::tuple using std::apply.

0
TruLa On

I came up with the following solution. Can anyone please tell me if it is a valid one?

#include <tuple>
#include <iostream>

void f0() {
  std::cout << "f0:" << std::endl;
}

void f1(int a) {
  std::cout << "f1: " << a << std::endl;
}

void f2(int a, int b) {
  std::cout << "f2: " << a << "," << b << std::endl;
}

template <typename... Tuples>
void execute(Tuples &&... tuples) {
  (std::apply(std::get<0>(tuples), std::get<1>(tuples)), ...);
}

int main() {
  execute(std::forward_as_tuple(f0, std::forward_as_tuple()),
          std::forward_as_tuple(f1, std::forward_as_tuple(1)),
          std::forward_as_tuple(f2, std::forward_as_tuple(3, 4)));
}