Search code examples
c++templateslambdavariadic-templates

How do I write a function that concatenates two functions?


I'm trying to write a generic function that concatenates two functions that can be called with the same set of arguments but I'm having a bit of trouble. Here's what I have so far (it doesn't compile)

//A functor to store the input functions and call them
template <typename LEFT, typename RIGHT>
struct combine_functions {
  combine_functions(const LEFT &left, const RIGHT &right)
   : left(left), right(right) {}

  template <typename ...ARGS>
  std::enable_if_t<
    //My compiler doesn't have support for C++17 std library so I 
    //found an implementation of callable on SO
    is_callable_v<LEFT, std::decay_t<ARGS>...> &&
    is_callable_v<RIGHT, std::decay_t<ARGS>...>
  > operator()(ARGS... args) const {
    //the return value doesn't matter in my situation and can be 
    //completely discarded
    left(std::forward<ARGS>(args)...);
    right(std::forward<ARGS>(args)...);
  }

private:
  mutable LEFT left;
  mutable RIGHT right;
};

//I should probably have an enable if that checks the arguments 
//are function pointers or functors
template <typename LEFT, typename RIGHT>
combine_functions<
  std::decay_t<LEFT>,
  std::decay_t<RIGHT>
>
operator+(
  const LEFT &left,
  const RIGHT &right
) {
  return {left, right};
}

If it is unclear what I'm trying to achieve then here is a test.

#include <iostream>
#include "combine functions.hpp"    

struct A {
  void operator()(float &f, int i) {
    std::cout << "running A with float " << f << " and int " << i << '\n';
    f++;
  }
};

struct B {
  void operator()(float &f, int i) {
    std::cout << "running B with float " << f << " and int " << i << '\n';
    f++;
  }
};

struct C {
  void operator()(float &f, int i) {
    std::cout << "running C with float " << f << " and int " << i << '\n';
    f++;
  }
};

int main(int, const char**) {
  A a;
  B b;
  C c;
  auto abc = concat(concat(a, b), c);
  //or
  //auto abc = a + b + c;
  std::function<void(float &, int)> abcFunc = abc;
  float f = 5.0f;
  int i = 9;
  abcFunc(f, i);

  return EXIT_SUCCESS;
}

And here is the expected output

running A with float 5 and int 9
running B with float 6 and int 9
running C with float 7 and int 9    
  • How do I implement this in C++?
  • Is it unwise to use an overloaded operator in this situation?
  • Is "concatenate" the best term for this operation?

Solution

  • I think this is a reasonable starting point. Supports any number of concatenations and any number of arguments with perfect forwarding:

    #include <tuple>
    #include <utility>
    #include <iostream>
    
    namespace detail 
    {
        template<class Tuple, std::size_t...Is, class...Args>
        void exec(Tuple&& tuple, std::index_sequence<Is...>, Args&&...args)
        {
            using expand = int[];
            void(expand{
                0,
                (std::get<Is>(tuple)(std::forward<Args>(args)...),0)...
            });
    
        }
    }
    
    template<class...Funcs>
    auto concat(Funcs&&...funcs)
    {
        constexpr auto nof_funcs = sizeof...(funcs);
        return [funcs = std::make_tuple(std::forward<Funcs>(funcs)...)](auto&&...args) mutable
        {
            detail::exec(funcs, 
                         std::make_index_sequence<nof_funcs>(), 
                         std::forward<decltype(args)>(args)...);
        };
    };
    
    int main()
    {
        auto f1 = [](auto&& arg) { std::cout << arg << std::endl; };
        auto f2 = [](auto&& arg) { std::cerr << arg << std::endl; };
    
        concat(f1, f2)("Hello, World");
    }