具有多重函子的 C++ 推送管道

C++ push-pipeline with multi-functors

是否可以将 3 个或更多的多重函数与管道功能和像管道这样的优雅语法链接在一起?

struct Filter0{//multi-functor; will be the last one in pipeline
  template<Args...args> void operator()(Args&&...args){/*not important what is done here*/}
};

template<std::invocable F> struct Filter1{//intermediate filter forwarding to a multi-functor
  template<Args...args> void operator()(F&f, Args&&...args){
    /*do smtg and sometime call f(std::forward<decltype(args)>(args)...);*/
  }
};

template<typename Left, std::invocable Right>
auto operator|(Left left, Right right){//chains "something" with a multi-functor
    return [left,right](auto&& arg){
        left(right, std::forward<decltype(arg)>(arg));
    };
}

Filter0 filter0;
Filter1<decltype(Filter0)> filter1;
auto pipe1 = filter1 | filter0;//OK
pipe1('x');//OK pushes 'x' down the pipeline

Filter2<decltype(Filter1)> filter2;//Filter2 would be something similar with Filter1
auto pipe2 = filter2 | (filter1 | filter0);//KO
pipe2('x');//KO

第一个 KO 行中有或没有括号:

注意:如果我提供“下一个”作为每个中间过滤器的 ctor 参数并将其作为成员存储在 class 中而不是在每个方法中作为参数,我可以“链接”3 个或更多过滤器,类似:

Filter0 filter0;
Filter1 filter1(filter0);
Filter2 filter2(filter1);
filter2('x');//this will push 'x' down the pipeline

但在这种情况下,它们在声明中彼此“紧密”。 我希望它们尽可能“松散”,仅按类型“紧”(如果可能的话甚至根本不)

你可以在这里玩一个活生生的例子:https://godbolt.org/z/6e8cfrbTo

解决方案 1(简单且简短):将模板参数从过滤器 class 移动到方法(感谢 RedFog 和 Jarod42!)

解决方案 2(更详细一点):使 filter|filter 操作成为表达式模板(参见 https://godbolt.org/z/G9z3qfsj8

关键问题是你要求F,过滤器应该接收的仿函数的类型,作为参数,但你实际上提供了一个lambda表达式。

auto pipe2 = filter2 | (filter1 | filter0);

pipe2Filter2<Filter1<Filter0>>,具有参数类型 Filter1<Filter0>,但您提供 [left,right](auto&& arg){ left(right, std::forward<decltype(arg)>(arg)); }; 而不是 Filter1<Filter0>.

的实例

最简单的解决方案是,让模板参数 F 属于 operator() 而不是过滤器本身。参见 Demo

编辑:我删除了所有 std::invocable,它没有按应有的方式执行。我认为operator|中的std::invocable约束意味着你想要filter | value,所以filter | (filter | (... | value)...),但这是不可能的,你设计的过滤器没有足够的能力来检查它接收一个值而不是另一个过滤器。

编辑:|是左联的,也许右联的设计会事半功倍。正如@Jarod42 所说,不受约束 operator| 是危险的。更好的方法是让你所有的过滤器和filter | filter的结果都在一个概念(也许命名为isFilter?)的约束下,这样你就可以filter | filter | ... filter甚至filter | filter | ... filter | value.

你的问题是 Filter2<decltype(Filter1)> filter2; 是错误的,应该是 Filter2<decltype(pipe1)> filter2; Demo.

但更简单的方法是将 F 从 class 参数移动到函数参数:

struct EolFilter{//chainable multi-functor; adds an EOL after each char
    template<typename F, typename...Args>
    requires(std::invocable<F, Args&&...>)
    void operator()(const F& f, Args&&...args)const{//pass-through unchanged
        printf("EolFilter::operator()(Args&&...args)\n");
        f(std::forward<decltype(args)>(args)...);
    }

    template<std::invocable<char> F>
    void operator()(const F& f, char c)const{
        printf("EolFilter::operator()(char '%c')\n", c);
        f(c);
        f('\n');
    }
};
struct VowelFilter{//chainable multi-functor; filters out vowels
    template<typename F, typename...Ts>
    requires(std::invocable<F, Ts&&...>)
    void operator()(const F& f, Ts&&...args)const{//pass-through unchanged
        f(std::forward<decltype(args)>(args)...);
    }

    template<std::invocable<char> F>
    void operator()(const F& f, char c)const{//
        if(c=='a' || c=='e' || c=='i' || c=='o' || c=='u'){
            //f("VOWEL");//skip or do something special for this case
        }else{
            f(c);//pass-through unchanged
        }
    }
};

Demo

注意:std::invocable用法已修复。

注意:unconstraint operator| 很危险,您可能应该遵循上一个问题 的建议。