How do I write a function that combines two functions?

I'm trying to write a generic function that combines two functions that can be called with the same set of arguments, but I'm having some problems. Here is what I have (does not 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 not clear what I am 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 result

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 wise to use an overloaded operator in this situation?
  • Is "concatenation" the best term for this operation?
+3


source to share


4 answers


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



#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");
}

      

+3


source


You can use the following:

template <typename LEFT, typename RIGHT>
struct combine_functions {
private:
  LEFT left;
  RIGHT right;
public:
  combine_functions(const LEFT& left, const RIGHT& right)
   : left(left), right(right) {}

  template <typename ...ARGS>
  auto operator()(ARGS&... args) const
  -> decltype(left(args...), static_cast<void>(right(args...)))
  {
    left(args...);
    right(args...);
  }

};

template <typename LEFT, typename RIGHT>
combine_functions<std::decay_t<LEFT>, std::decay_t<RIGHT>>
concat(const LEFT& left, const RIGHT& right)
{
  return {left, right};
}

      



Demo

I don't use operator +

which is too general and fits too large a type. I don't use std::forward

as you don't want move

( right

will call moved objects ...)

+2


source


How do I implement this in C ++?

I usually don't just write code for someone, but it was simple enough.

#include <iostream>
#include <functional>
#include <string>

using namespace std;

template <typename Left, typename Right>
class ConcatFn {
public:
    ConcatFn(Left left, Right right)
            : left(left)
            , right(right) {
    }

    template <typename... Args>
    void operator()(Args... args) {
        this->left(args...);
        this->right(args...);
    }

private:
    function<Left> left;
    function<Right> right;
};

void A(const char *foo) {
    cout << "A: " << foo << endl;
}

void B(string bar) {
    cout << "B: " << bar << endl;
}

int main() {
    ConcatFn<void(const char *), void(string)> fn(A, B);

    fn("hello!");

    return 0;
}

      

Outputs:

$ ./concat
A: hello!
B: hello!

      

I don't think you will get away with the template arguments in the declaration fn

above.

Also, if you want to ensure that the functions are accurately signed, just remove the second template argument ( Right

) and use Left

(ideally rename it) everywhere Right

.

Is it unwise to use an overloaded operator in this situation?

Not at all.

Is "concatenation" the best term for this operation?

Probably not, but seeing how rare this use case is, I don't know of a single "standard" term for this. Functional chain or grouping perhaps?

+1


source


Looks right, just a few quirks here and there

#include<type_traits>
#include<utility>

template<typename L, typename R>
struct combined
{
    typename std::decay<L>::type l;
    typename std::decay<R>::type r;
    combined(L&& l, R&& r)
       : l(std::forward<L>(l)), r(std::forward<R>(r)) {}

    template<typename... Args>
    void operator()(Args&&... args)
    {
        l(args...);
        r(std::forward<Args>(args)...);
    }
};

template<typename L, typename R>
combined<L, R> combine(L&& l, R&& r)
{
    return {std::forward<L>(l), std::forward<R>(r)};
}

      

First, callable objects most likely need to be persisted hence std::remove_reference

.

std::enable_if

ais is unnecessary as the compiler will emit an error when the object is called incorrectly.

std::forward

is known as perfect forwarding and is essential.

EDIT3 After the debate, the first one forward

was filmed to prevent unintentional moves, thanks to Rerito, R. Martino Fernandez and Jarod

operator+

feels really weird as you are not adding return values, it conflicts with the definition of adding a function.

0


source







All Articles