如何编写连接两个函数的函数?

时间:2017-05-15 08:31:06

标签: c++ templates lambda variadic-templates

我正在尝试编写一个泛型函数,它连接两个可以用同一组参数调用的函数,但是我遇到了一些麻烦。这是我到目前为止所拥有的。 (不编译)

//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};
}

如果不清楚我想要实现的目标,那么这是一个测试。

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

这是预期的输出

running A with float 5 and int 9
running B with float 6 and int 9
running C with float 7 and int 9    
  • 如何在C ++中实现此功能?
  • 在这种情况下使用重载运算符是不明智的吗?
  • “连接”是此操作的最佳术语吗?

提前感谢您的帮助。

4 个答案:

答案 0 :(得分:3)

我认为这是一个合理的起点。支持任意数量的连接和任意数量的具有完美转发的参数:

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

答案 1 :(得分:2)

您可以使用以下内容:

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

我不使用过于通用且匹配太多类型的operator +。 我不使用std::forward,因为您不希望moveright会调用移动的对象...)

答案 2 :(得分:1)

  

如何在C ++中实现它?

我通常不会只为某人编写代码,但这很简单。

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

输出:

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

我认为你不会放弃上面fn声明中的模板参数。

此外,如果您想保证函数具有确切的签名,只需删除第二个模板参数(Right)并使用Left(理想情况下重命名)到处Right是使用

  

在这种情况下使用重载运算符是不明智的吗?

完全没有。

  

“连接”是此操作的最佳术语吗?

可能不是,但看看这个用例是多么罕见,我不知道任何“标准”术语。函数链接或分组,也许?

答案 3 :(得分:0)

看起来正确,只是在这里和那里有几个怪癖

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

首先,最可能必须存储可调用对象,因此std::remove_reference

std::enable_if ais是不必要的,因为当对象被错误地调用时,编译器会发出错误。

std::forward被称为perfect forwarding,非常重要。

EDIT3 经过辩论,第一个forward被取消以防止意外动作发生,感谢Rerito,R。Martinho Fernandes和Jarod42

operator+感觉非常奇怪,因为您没有添加返回的值,这与函数添加的定义冲突。