在c++ 0x中如何重载操作符来组合函数



是否有一种方法可以重载,例如用于函数组合的>>操作符?操作人员应该在lambda和std::function上无缝工作?

要求:

  • 解决方案不应该包含嵌套的bind调用,
  • 左操作数可以是具有任意数量参数的函数类型,并且
  • 只能创建一个函数对象实例。

下面是一个简单的例子,说明了期望的行为:

#include <iostream>
#include <functional>
using namespace std;
// An example of a quick and dirty function composition.
// Note that instead of 'std::function' this operator should accept
// any functional/callable type (just like 'bind').
template<typename R1, typename R2, typename... ArgTypes1>
function<R2(ArgTypes1...)> operator >> (
                const function<R1(ArgTypes1...)>& f1,
                const function<R2(R1)>& f2) {
    return [=](ArgTypes1... args){ return f2(f1(args...)); };
}
int main(int argc, char **args) {
    auto l1 = [](int i, int j) {return i + j;};
    auto l2 = [](int i) {return i * i;};
    function<int(int, int)> f1 = l1;
    function<int(int)> f2 = l2;
    cout << "Function composition: " << (f1 >> f2)(3, 5) << endl;
    // The following is desired, but it doesn't compile as it is:
    cout << "Function composition: " << (l1 >> l2)(3, 5) << endl;
    return 0;
}

(l1 >> l2)不能工作

它们是由编译器生成的函数对象,不包括该操作符,所以除非你打算修改编译器使其不符合标准,否则它总是会是这样的。:)

然而,您可以引入一个"关键字"(实用程序类),这可以说是一件好事,但它是沉重的:
// https://ideone.com/MS2E3
#include <iostream>
#include <functional>
namespace detail
{
    template <typename R, typename... Args>
    class composed_function;
    // utility stuff
    template <typename... Args>
    struct variadic_typedef;
    template <typename Func>
    struct callable_type_info :
        callable_type_info<decltype(&Func::operator())>
    {};
    template <typename Func>
    struct callable_type_info<Func*> :
        callable_type_info<Func>
    {};
    template <typename DeducedR, typename... DeducedArgs>
    struct callable_type_info<DeducedR(DeducedArgs...)>
    {
        typedef DeducedR return_type;
        typedef variadic_typedef<DeducedArgs...> args_type;
    };
    template <typename O, typename DeducedR, typename... DeducedArgs>
    struct callable_type_info<DeducedR (O::*)(DeducedArgs...) const>
    {
        typedef DeducedR return_type;
        typedef variadic_typedef<DeducedArgs...> args_type;
    };
    template <typename DeducedR, typename... DeducedArgs>
    struct callable_type_info<std::function<DeducedR(DeducedArgs...)>>
    {
        typedef DeducedR return_type;
        typedef variadic_typedef<DeducedArgs...> args_type;
    };
    template <typename Func>
    struct return_type
    {
        typedef typename callable_type_info<Func>::return_type type;
    };
    template <typename Func>
    struct args_type
    {
        typedef typename callable_type_info<Func>::args_type type;
    };
    template <typename FuncR, typename... FuncArgs>
    struct composed_function_type
    {
        typedef composed_function<FuncR, FuncArgs...> type;
    };
    template <typename FuncR, typename... FuncArgs>
    struct composed_function_type<FuncR, variadic_typedef<FuncArgs...>> :
        composed_function_type<FuncR, FuncArgs...>
    {};
    template <typename R, typename... Args>
    class composed_function
    {
    public:
        composed_function(std::function<R(Args...)> func) :
        mFunction(std::move(func))
        {}
        template <typename... CallArgs>
        R operator()(CallArgs&&... args)
        {
            return mFunction(std::forward<CallArgs>(args)...);
        }
        template <typename Func>
        typename composed_function_type<
                    typename return_type<Func>::type, Args...>::type
             operator>>(Func func) /* && */ // rvalues only (unsupported for now)
        {
            std::function<R(Args...)> thisFunc = std::move(mFunction);
            return typename composed_function_type<
                                typename return_type<Func>::type, Args...>::type(
                                        [=](Args... args)
                                        {
                                            return func(thisFunc(args...));
                                        });
        }
    private:    
        std::function<R(Args...)> mFunction;
    };
}
template <typename Func>
typename detail::composed_function_type<
            typename detail::return_type<Func>::type,
                typename detail::args_type<Func>::type>::type
    compose(Func func)
{
    return typename detail::composed_function_type<
                        typename detail::return_type<Func>::type,
                            typename detail::args_type<Func>::type>::type(func);
}
int main()
{
    using namespace std;
    auto l1 = [](int i, int j) {return i + j;};
    auto l2 = [](int i) {return i * i;};
    std:function<int(int, int)> f1 = l1;
    function<int(int)> f2 = l2;
    cout << "Function composition: " << (compose(f1) >> f2)(3, 5) << endl;
    cout << "Function composition: " << (compose(l1) >> l2)(3, 5) << endl;
    cout << "Function composition: " << (compose(f1) >> l2)(3, 5) << endl;
    cout << "Function composition: " << (compose(l1) >> f2)(3, 5) << endl;
    return 0;

这是相当多的代码!遗憾的是,我看不出还有什么办法可以降低。

你可以走另一条路,在你的方案中使用lambda,你只需要显式地使它们成为std::function<> s,但它不那么统一。上面的一些机制可以用来创建某种to_function()函数,将lambda函数转换为std::function<> s。

这段代码应该可以完成这项工作。我已经简化了它,所以这些只适用于有一个参数的函数,但是你应该能够通过一些可变模板魔法来扩展它,让它可以接受多个参数的函数。你还可以限制<<适当的算子。

#include <iostream>
template <class LAMBDA, class ARG>
auto apply(LAMBDA&& l, ARG&& arg) -> decltype(l(arg))
{
  return l(arg);
}
template <class LAMBDA1, class LAMBDA2>
class compose_class
{
public:
  LAMBDA1 l1;
  LAMBDA2 l2;
  template <class ARG>
  auto operator()(ARG&& arg) -> 
    decltype(apply(l2, apply(l1, std::forward<ARG>(arg))))
  { return apply(l2, apply(l1, std::forward<ARG>(arg))); }
  compose_class(LAMBDA1&& l1, LAMBDA2&& l2) 
    : l1(std::forward<LAMBDA1>(l1)), l2(std::forward<LAMBDA2>(l2)) {}
};
template <class LAMBDA1, class LAMBDA2>
auto operator>>(LAMBDA1&& l1, LAMBDA2&& l2) -> compose_class<LAMBDA1, LAMBDA2>
{
  return compose_class<LAMBDA1, LAMBDA2>
    (std::forward<LAMBDA1>(l1), std::forward<LAMBDA2>(l2));
}
int main()
{    
  auto l1 = [](int i) { return i + 2; };
  auto l2 = [](int i) { return i * i; };
  std::cout << (l1 >> l2)(3) << std::endl;
}

(注。您可能不需要"apply"的间接作用,只是在没有它的情况下编译时会遇到一些麻烦)

虽然GMan注意到l1 >> l2永远不会起作用,但类似的东西确实起作用,甚至产生了一些漂亮的结果。将以下内容添加到他的代码中:

class compose_syntax_helper_middle
{
} o;
template <typename Func>
typename detail::composed_function_type<
typename detail::return_type<Func>::type,
    typename detail::args_type<Func>::type>::type
    operator<< (Func func, compose_syntax_helper_middle)
{
    return typename detail::composed_function_type<
        typename detail::return_type<Func>::type,
                 typename detail::args_type<Func>::type>::type(func);
}

现在这个语法可以工作了:

(func1 <<o>> func2) (arg1, arg2)

<< >>操作符被解释为一种引号,o是一种函数组合圈的东西…

这个怎么样?

#include <cstdio>
#include <functional>
template <typename F, typename F_ret, typename... F_args,
          typename G, typename G_ret, typename... G_args>
std::function<G_ret (F_args...)>
     composer(F f, F_ret (F::*)(F_args...) const ,
              G g, G_ret (G::*)(G_args...) const)
{
  // Cannot create and return a lambda. So using std::function as a lambda holder.
  std::function<G_ret (F_args...)> holder;
  holder = [f, g](F_args... args) { return g(f(args...)); };
  return holder;
}
template<typename F, typename G>
auto operator >> (F f, G g)
  -> decltype(composer(f, &F::operator(), g, &G::operator()))
{
  return composer(f, &F::operator(), g, &G::operator());
}
int main(void)
{
  auto l1 = [](int i , int j) { return i + j; };
  auto l2 = [](int a) { return a*a; };
  printf("%dn", (l1 >> l2 >> l2)(2, 3)); // prints 625
  return 0;
}
编辑:

下面是一些增强的代码,支持自由函数指针和成员函数指针。我也有一些测试代码。在执行这种深度组合的std::function对象时,要注意发生的虚函数调用的数量。我认为每个std::function对象的operator()都有一个虚函数调用。内存分配和释放是你必须记住的另一件事。
#include <cstdio>
#include <functional>
template <typename F, typename F_ret, typename... F_args, 
          typename G, typename G_ret, typename... G_args> 
std::function<G_ret (F_args...)>
     composer(F f, F_ret (F::*)(F_args...) const , 
              G g, G_ret (G::*)(G_args...) const)
{
  // Cannot create and return a lambda. So using std::function as a lambda holder.
  std::function<G_ret (F_args...)> holder;
  holder = [f, g](F_args... args) { return g(f(args...)); };  
  return holder;
}
template<typename F_ret, typename... F_args>
std::function<F_ret (F_args...)> 
make_function (F_ret (*f)(F_args...))
{
  // Not sure why this helper isn't available out of the box.
  return f;
}
template<typename F, typename F_ret, typename... F_args>
std::function<F_ret (F_args...)>
make_function (F_ret (F::*func)(F_args...), F & obj)
{
  // Composing a member function pointer and an object.  
  // This one is probably doable without using a lambda. 
  std::function<F_ret (F_args...)> holder;
  holder = [func, &obj](F_args... args) { return (obj.*func)(args...); };  
  return holder;
}
template<typename F, typename F_ret, typename... F_args>
std::function<F_ret (F_args...)>
make_function (F_ret (F::*func)(F_args...) const, F const & obj)
{
  // Composing a const member function pointer and a const object.  
  // This one is probably doable without using a lambda. 
  std::function<F_ret (F_args...)> holder;
  holder = [func, &obj](F_args... args) { return (obj.*func)(args...); };  
  return holder;
}
template<typename F, typename G>
auto operator >> (F f, G g)
  -> decltype(composer(f, &F::operator(), g, &G::operator())) 
{ 
  return composer(f, &F::operator(), g, &G::operator()); 
}
// This one allows a free function pointer to be the second parameter 
template<typename F, typename G_ret, typename... G_args>
auto operator >> (F f, G_ret (*g)(G_args...))
  -> decltype(f >> make_function(g)) 
{ 
  return f >> make_function(g); 
}
// This one allows a free function pointer to be the first parameter 
template<typename F, typename G_ret, typename... G_args>
auto operator >> (G_ret (*g)(G_args...), F f)
  -> decltype(make_function(g) >> f) 
{ 
  return make_function(g) >> f; 
}
// Not possible to have function pointers on both sides of the binary operator >>
int increment(int i) {
  return i+1;
}
int sum(int i, int j) {
  return i+j;
}
struct math {
  int increment (int i) {
    return i+1;
  }
  int sum (int i, int j) const {
    return i+j;
  }
};
int main(void)
{
  auto l1 = [](int i , int j) { return i + j; };
  auto l2 = [](int a) { return a*a; };
  auto l3 = l1 >> l2 >> l2 >> increment; // does 11 allocs on Linux
  printf("%dn", l3(2, 3));              // prints 626
  printf("%dn", (sum >> l2)(3, 3));     // prints 36
  math m;
  printf("%dn", 
   (make_function(&math::sum, m) >> make_function(&math::increment, m))(2, 3)); // prints 6

  return 0;
}

如果您想接受任何类型的函数对象,您的operator >>应该接受大多数一般类型。

 template<typename F, typename G>
 class compose {...}
 template<typename f, typename g>
 compose <F, G> operator >> (F f, G g)
 { return compose<F, G>(f, g); }

应该很容易弄清楚compose::operator()应该做什么。提示:必须是模板

UPD:显然,c++在将operator >>应用于内置函数类型时不高兴。这不是一个大问题,语法可以稍加修改。更大的问题是,g++-4.6.0(我目前拥有的唯一支持部分c++0x的编译器)似乎无法处理实现这一功能所需的语言特性。它给了我一些非常奇怪的错误,有时是内部编译器错误。我将尝试升级到4.6.1,看看会发生什么。

最新更新