C++ 中的泛型多功能 composition/pipelining
Generic multi-functor composition/pipelining in C++
是否可以在 C++ 20 中实现泛型多重函子 composition/pipelining?
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_dude 和@Jarod42),但我迷失了错误:
- 像@Some_programmer_dude 建议的那样重载运算符|()
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> ]
- 直接使用 lambda 而不是像 @Jarod42 建议的那样重载 operator|():
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;
}
};
添加标签和 return 值(否则,f(g(x)) 没有任何意义,除非 g returns something)。
您编写的代码现在可以运行了。
如果您愿意,我们还可以添加对 std::function
甚至原始函数的支持。您将在 namespace ops
中添加合适的 operator|
重载,并要求人们 using ops::operator|
将运算符引入作用域(或将其与 op_tag
类型一起使用)。
输出:
f(123)g(-1)f(text)g(-1)
你快到了
template<class Inp, class Out>
auto operator|(Inp inp, Out out){
return [inp,out](const Inp& arg){
out(inp(arg));
};
}
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;
但是您忽略了一些细微差别。
首先,lambda 中的 out
和 inp
是 const
,因为闭包类型具有 const
限定的 operator()
。另外,lambda 的参数类型应该是 auto
,而不是 Inp
,否则你只能 pipe(f)
.
您想要为某些 x
调用 g(f(x))
,但是 f
的 return 类型是 void
,这将需要一些东西否则。
您提供的 |
将与算术 |
.
产生歧义
struct some_type {};
template<typename F>
concept unary_invocable = std::invocable<F, some_type>;
template <unary_invocable Inp, unary_invocable Out>
auto operator|(Inp inp, Out out){
return [inp,out](auto&& arg){
return out(inp(std::forward<decltype(arg)>(arg)));
};
}
struct F{//1st multi-functor
template<typename T> auto operator()(const T& t) const {/*... return something*/}
};
struct G{//2nd multi-functor
template<typename T> auto operator()(const T& t) const {/*... return something*/}
};
是否可以在 C++ 20 中实现泛型多重函子 composition/pipelining?
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_dude 和@Jarod42),但我迷失了错误:
- 像@Some_programmer_dude 建议的那样重载运算符|()
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> ]
- 直接使用 lambda 而不是像 @Jarod42 建议的那样重载 operator|():
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;
}
};
添加标签和 return 值(否则,f(g(x)) 没有任何意义,除非 g returns something)。
您编写的代码现在可以运行了。
如果您愿意,我们还可以添加对 std::function
甚至原始函数的支持。您将在 namespace ops
中添加合适的 operator|
重载,并要求人们 using ops::operator|
将运算符引入作用域(或将其与 op_tag
类型一起使用)。
输出:
f(123)g(-1)f(text)g(-1)
你快到了
template<class Inp, class Out>
auto operator|(Inp inp, Out out){
return [inp,out](const Inp& arg){
out(inp(arg));
};
}
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;
但是您忽略了一些细微差别。
首先,lambda 中的 out
和 inp
是 const
,因为闭包类型具有 const
限定的 operator()
。另外,lambda 的参数类型应该是 auto
,而不是 Inp
,否则你只能 pipe(f)
.
您想要为某些 x
调用 g(f(x))
,但是 f
的 return 类型是 void
,这将需要一些东西否则。
您提供的 |
将与算术 |
.
struct some_type {};
template<typename F>
concept unary_invocable = std::invocable<F, some_type>;
template <unary_invocable Inp, unary_invocable Out>
auto operator|(Inp inp, Out out){
return [inp,out](auto&& arg){
return out(inp(std::forward<decltype(arg)>(arg)));
};
}
struct F{//1st multi-functor
template<typename T> auto operator()(const T& t) const {/*... return something*/}
};
struct G{//2nd multi-functor
template<typename T> auto operator()(const T& t) const {/*... return something*/}
};