C++中的泛型多功能函数组合/流水线 [英] Generic multi-functor composition/pipelining in C++

查看:45
本文介绍了C++中的泛型多功能函数组合/流水线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以在C++20中实现泛型多功能函数组合/流水线?

struct F{//1st multi-functor
  template<typename T> void operator()(const T& t){/*...*/}
};
struct G{//2nd multi-functor
  template<typename T> void operator()(const T& t){/*...*/}
};

F f;
G g;
auto pipe = f | g;//what magic should happen here to achieve g(f(...)) ? how exactly to overload the operator|()?

pipe(123);   //=> g(f(123);
pipe("text");//=> g(f("text");

编辑: 我尝试了这两个建议(来自@Some_Programmer_DUD和@Jarod42),但我在错误中迷失了方向:

  1. 重载运算符|()Like@Some_Programmer_DUD建议
template<class Inp, class Out>
auto operator|(Inp inp, Out out){
    return [inp,out](const Inp& arg){
        out(inp(arg));
    };
}

生成:

2>main.cpp(71,13): error C3848: expression having type 'const Inp' would lose some const-volatile qualifiers in order to call 'void F::operator ()<F>(const T &)'
2>        with
2>        [
2>            Inp=F
2>        ]
2>        and
2>        [
2>            T=F
2>        ]
  1. 像@Jarod42建议的那样,直接使用lambda而不是重载运算符|():
auto pipe = [=](const auto& arg){g(f(arg));};

生成:

2>main.cpp(86,52): error C3848: expression having type 'const F' would lose some const-volatile qualifiers in order to call 'void F::operator ()<_T1>(const T &)'
2>        with
2>        [
2>            _T1=int,
2>            T=int
2>        ]

推荐答案

这里有一个快速的小型库。

#define RETURNS(...) 
  noexcept(noexcept(__VA_ARGS__)) 
  -> decltype(__VA_ARGS__) 
  { return __VA_ARGS__; }

namespace ops {
  template<class D>
  struct op_tag;

  template<class Second, class First>
  struct pipe_t;

  template<class D>
  struct op_tag {
    D const& self() const { return *static_cast<D const*>(this); }
    D& self() { return *static_cast<D*>(this); }

    auto operator()(auto&&...args) const
      RETURNS( self()(decltype(args)(args)...) )
    auto operator()(auto&&...args)
      RETURNS( self()(decltype(args)(args)...) )
  };
  
  template<class Second, class First>
  struct pipe_t:op_tag<pipe_t<Second, First>> {
    Second second;
    First first;
    pipe_t( Second second_, First first_ ):
      second(std::move(second_)),
      first(std::move(first_))
    {}
    auto operator()(auto&&...args)
      RETURNS( second(first(decltype(args)(args)...)) )
    auto operator()(auto&&...args) const
      RETURNS( second(first(decltype(args)(args)...)) )
  };
  template<class Second, class First>
  auto operator|(op_tag<First> const& first, op_tag<Second> const& second)
    RETURNS( pipe_t<Second, First>{ second.self(), first.self() } )
}

以贪婪的方式重载操作符被认为是粗鲁的。您只希望您的运算符重载参与您特别支持的类型。

在这里,我要求从op_tag<T>继承的类型指示它们对操作感兴趣。

然后我们对您的代码稍作修改:

struct F:ops::op_tag<F>{//1st multi-functor
  template<typename T>
  auto operator()(const T& t){
      std::cout << "f(" << t << ")";
      return -1;
  }
};
struct G:ops::op_tag<G>{//2nd multi-functor
  template<typename T> auto operator()(const T& t){
      std::cout << "g(" << t << ")";
      return 7;
  }
};

添加标记和返回值(否则,f(g(X))没有意义,除非g返回某些内容)。

您编写的代码现在可以工作了。

如果您愿意,我们还可以添加对std::function甚至原始函数的支持。您将在namespace ops中添加合适的operator|重载,并要求用户using ops::operator|将运算符纳入作用域(或将其与op_tag‘d’类型一起使用)。

Live example

输出:

f(123)g(-1)f(text)g(-1)

这篇关于C++中的泛型多功能函数组合/流水线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆