容器和函数都有一个共同的抽象.我在Haskell中学到了它,我正在尝试用C++实现它.
大多数C++程序员都熟悉std :: transform,粗略地说,从A类到B类的函数,你可以将类型A的容器转换为类型B的容器.
您可以以类似的方式转换函数,给定从A到B的函数foo,您可以将将Z转换为A的函数栏转换为函数foo.将Z带到B.实现很简单,它只是组合.
我想在容器和函数上定义一个函数fmap,以反映泛型编程的这种抽象.
容器很容易(我知道这不完全一般)
template <typename A, typename Func>
auto fmap(Func f, vector<A> in) {
vector<decltype(f(in[0]))> out_terms{};
for(auto vec : in)
out_terms.push_back(f(vec));
return out_terms;
}
Run Code Online (Sandbox Code Playgroud)
然而,功能的类似功能让我更加紧张.
template <typename FuncT, typename Func>
auto fmap(FuncT f, Func in) {
return [f, in](auto x){
return f(in(x));
};
}
Run Code Online (Sandbox Code Playgroud)
虽然模板不会专门用于除可调用事物之外的任何东西,但我担心这会混淆重载决策.我想在模板参数上引入类型约束,以限制它们对函数类型的解析,以保持名称空间的清洁.我打算问如何做到这一点.
这种抽象非常通用,对于指向值的指针有相应的fmaps,我怀疑它也可能会发生冲突.
所以我认为我的问题是,我可以使用相同的模板级签名进行两种不同的模板实现吗?我几乎可以肯定答案是肯定的,但也许类似的事情可能会被伪造.如果没有,今天可以使用哪些工具来区分过载?特别是对于功能类型.
对我而言,这似乎是概念的教科书案例,尽管我不确定.
编辑:使用Boost是可以接受的,特别是SFINAE.我试图找到一个大多数程序员都熟悉的解决方案,尽可能方便,规范.我可以将fmap重命名为compose,但程序员必须知道将compose传递给接受fmap的模板函数.那将是不幸的,因为fmap在语义上是唯一的.
编辑2:如何使用它的一个简单示例.
template <typename T>
auto double_everything(T in){
auto doublef = [](auto x){return 2*x;};
return fmap(doublef, in);
}
Run Code Online (Sandbox Code Playgroud)
它将容器上的映射概括为映射到"容器之类"的东西.因此double_everything(vector<int> {1, 2, 3})返回一个向量,其元素加倍.但double_everything([](int x){ return x + 1; })返回一个函数,其输出是递增函数输出的两倍.这就像加倍一种清单.抽象有一些很好的属性,我不仅仅是弥补它.无论如何,将函数重命名fmap为compose并不能回答这个问题.
编辑3:
fmap为模板C从函数需要A向B到功能,从C<A>到C<B>和满足fmap( compose(f, g) , c ) = fmap( f, fmap( g, c )).这是一个很好的结构保留财产.
对范围执行此操作的函数已经以不同的名称存在.但范围不是类型上的唯一模板.这是fmap为std::optional:
template<typename T, typename Func>
auto fmap(Func f, optional<T> o) -> optional<f(*o)>{
if(o)
return f(*o);
else
{};
}
Run Code Online (Sandbox Code Playgroud)
此实现根本不涉及任何范围概念,例如fmap前面介绍的for函数.但它满足了语义要求fmap.
我试图定义fmap不同的重载,就像我operator *为自定义矩阵类型定义一个新的一样.所以,我会愉快地定义fmap来讲boost::transform_iterator.然后,这些算法将使用泛型函数fmap.
以下是此类功能的示例:
template <
template<typename, typename> class Cont,
typename Fmappable,
typename Alloc,
typename Func>
auto map_one_deep(Func f, Cont<Fmappable, Alloc> c){
auto g = [f](Fmappable x){ return fmap(f, x); };
return fmap(g, c);
}
Run Code Online (Sandbox Code Playgroud)
现在,如果我们写
auto lists = vector<vector<int> > { {1, 2, 3}, {4, 5, 6} };
auto lists_squared = map_one_deep( [](int x){return x*x;} , lists);
Run Code Online (Sandbox Code Playgroud)
lists_squared 印刷的
1 4 9
16 25 36
Run Code Online (Sandbox Code Playgroud)
如果我们有一个选项向量,那么只要它们包含元素,选项就会被平方.
我试图理解如何在c ++中使用高阶函数.
这是我发现的最简单的妥协
template <typename FuncT, typename O, typename T>
auto fmap(FuncT f, function<O(T)> in){
return [f, in](T x){
return f(in(x));
};
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,这需要function<Output(Input)>装饰调用站点,并且它会浪费间接性。我很确定如果需要约束,这是最好的方法。fmap
编辑:你可以做得更好。该链接提供了一种限制可调用对象的方法,该方法也是内联的。
该函数可以这样写:
template <typename FuncT, typename T>
auto fmap(FuncT f, tagged_lambda<T> in){
return tag_lambda([f, in](T x){
return f(in(x));
});
}
Run Code Online (Sandbox Code Playgroud)
您可以通过致电在呼叫站点选择您想要的版本
fmap(g, tag_lambda({}(int x){return x + 1;}) );
或者
fmap(g, function<int(int)>({}(int x){return x + 1;}) );
考虑到模板的工作原理,我非常确定需要标记该函数。
这是一篇博客文章,其中也讨论了这个问题,并讨论了其他选项。 http://yapb-soc.blogspot.com/2012/10/fmap-in-c.html。