【问题标题】:C++ push-pipeline with multi-functors具有多功能的 C++ 推送管道
【发布时间】:2021-06-18 11:48:18
【问题描述】:

是否可以在管道功能和优雅的语法(如管道)中链接 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 行中有或没有括号:

  • 我认为带有括号的“(filter1 | filter0)”应该以某种方式返回一个仿函数
  • 没有括号我认为“filter2 | filter1”应该以某种方式返回一个部分函数

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

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

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

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

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

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

【问题讨论】:

  • 你的问题是Filter2&lt;decltype(Filter1)&gt; filter2是错的,应该是Filter2&lt;decltype(pipe1)&gt; filter2Demo
  • F 是类的模板参数而不是函数的模板参数有什么原因吗?
  • std::invocable 的用法很奇怪:你期望f(),但用不同类型的参数调用它。

标签: c++ templates pipeline functor


【解决方案1】:

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

auto pipe2 = filter2 | (filter1 | filter0);

pipe2Filter2&lt;Filter1&lt;Filter0&gt;&gt;,具有参数类型Filter1&lt;Filter0&gt;,但您提供[left,right](auto&amp;&amp; arg){ left(right, std::forward&lt;decltype(arg)&gt;(arg)); }; 而不是Filter1&lt;Filter0&gt; 的实例。

最简单的解决方案是,让模板参数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

【讨论】:

  • 错误使用std::invocable,作为OP。
  • @Jarod42 是的,还有std::invocable的误用,我没有修复它,因为它不是关键问题。 OP似乎检查了invocability来区分filter | valuefilter | filter,这可能是设计问题。
  • 不使用约束比使用错误的约束要好。 (在 OP 的情况下使用 std::invocable&lt;char&gt; 至少部分正确,请参阅我的答案以了解正确的方法)
  • @Jarod42 你是对的 std::invocableunconstraint operator| 但这些不是关键点 @RedFog “右关联设计将获得一半的结果,事半功倍”......你意思相反?否则为什么要提到它?右关联设计意味着: - 使用右关联运算符(如|=):pipe = first |= ... |= last; - 或恢复过滤器顺序:pipe = last | ... | first; 我说得对吗?
  • @Solo 是的,使用右关联运算符或恢复顺序也可以,但我也建议 制作所有过滤器和 filter | filter 的结果 在一个概念的约束下,为它们定义运算和规律是安全和容易的。我提到的更好的方法是表达式模板,这意味着filter | filter也是一个过滤器,并且具有关联法则,如(filter | filter) | (filter | filter)。跨度>
【解决方案2】:

您的问题是 Filter2&lt;decltype(Filter1)&gt; filter2; 错误,应该是 Filter2&lt;decltype(pipe1)&gt; filter2; Demo

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

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| 很危险,您可能应该遵循上一个问题generic-multi-functor-composition-pipelining-in-c 的建议。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-15
    • 1970-01-01
    • 2021-09-01
    • 2021-09-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多