使用模板重载函数类型

overloading over function types with templates

本文关键字:函数 类型 重载      更新时间:2023-10-16

容器和函数都有一个共同的抽象。我在 Haskell 中学到了它,我正在尝试在 C++ 中实现它。

大多数C++程序员都熟悉 std::transform,粗略地说,给定一个从 A 型到 B 型的函数,你可以将 A 型的容器转换为 B 型的容器。

你可以用类似的方式转换函数,给定一个函数foo

从A到B,你可以将一个函数栏将Z转换为A转换为一个函数foo。实现很简单,只是组合。

我想在容器和函数上定义一个函数 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;
}

然而,函数的类似函数让我更加紧张。

template <typename FuncT, typename Func>
auto fmap(FuncT f, Func in) {
  return [f, in](auto x){
    return f(in(x));
  };
}

尽管该模板不会专门用于可调用内容以外的任何内容,但我担心这会混淆重载分辨率。我想在模板参数上引入类型约束,以将其分辨率限制为函数类型,以保持命名空间干净。我正要问怎么做。

这种抽象是非常通用的,有相应的fmap用于指向值的指针,我怀疑这也可能会发生冲突。

所以我认为我的问题是,我可以有两个具有相同模板级别签名的不同模板实现吗?我几乎可以肯定答案是否定的,但也许可以伪造类似的东西。如果没有,今天有什么工具可以区分过载?特别是对于功能类型。

在我看来,这似乎是概念的教科书案例,尽管我不确定。

编辑:Boost是可以接受的,特别是SFINAE。我试图找到一个大多数程序员都熟悉的解决方案,并且尽可能方便和规范。我可以将 fmap 重命名为 compose,但程序员必须知道将 comppose 传递给接受 fmap 的模板函数。这将是不幸的,因为 fmap 在语义上是唯一的。

编辑 2:如何使用它的简单示例。

template <typename T>
auto double_everything(T in){
  auto doublef = [](auto x){return 2*x;};
  return fmap(doublef, in);
}

它将容器上的映射推广到"类似容器"事物的映射。因此,double_everything(vector<int> {1, 2, 3})返回一个元素加倍的向量。但double_everything([](int x){ return x + 1; })返回一个函数,其输出是增量函数输出的两倍。这就像将一种列表加倍。抽象有一些很好的属性,我不只是编造它。无论如何,将函数重命名为组合fmap并不能回答这个问题。

编辑3: 模板fmap C将函数从ABC<A>C<B>,并满足fmap( compose(f, g) , c ) = fmap( f, fmap( g, c ))。这是一个很好的结构保存属性。

对范围执行此操作的函数已经以不同的名称存在。但范围并不是类型上的唯一模板。以下是std::optional fmap

template<typename T, typename Func>
auto fmap(Func f, optional<T> o) -> optional<f(*o)>{
  if(o)
    return f(*o);
  else
    {};
}

此实现根本不涉及任何范围概念,如前面介绍的函数fmap。但它满足了fmap的语义要求。

我正在尝试为不同的重载定义fmap,就像为自定义矩阵类型定义新operator *一样。所以我很乐意用boost::transform_iterator来定义fmap.然后这些算法将与函数泛型一起使用 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);
}

现在如果我们写

auto lists = vector<vector<int> > { {1, 2, 3}, {4, 5, 6} };
auto lists_squared = map_one_deep( [](int x){return x*x;} , lists);

lists_squared印刷给

1 4 9
16 25 36

如果我们有一个可选的向量,那么只要它们包含元素,这些可选将被平方。

我试图了解应该如何在 c++ 中使用高阶函数。

你可以用SFINAE伪造它,但你不应该。这是一个风格和成语的问题。

Haskell是关于类型类的,程序员期望必须将每个类型与它所属的所有俱乐部联系起来。相比之下,C++希望在指定类型的功能时更加隐式。你已经在那里显示了"vector"和"任意可调用",但为什么只是vector?为什么不是任意容器类型?而我刚刚写的这个任意容器类型有一个operator(),因为原因。那么它应该选择哪一个呢?

底线,虽然您可以使用 SFINAE 技巧来解决技术歧义,但您不应该使用它们来解决基本的歧义。只需使用两个不同的名称。

这是我

发现的最简单的折衷方案

template <typename FuncT, typename O, typename T>
auto fmap(FuncT f, function<O(T)> in){
  return [f, in](T x){
    return f(in(x));
  };
}

不幸的是,这需要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));
  });
}

您可以在呼叫站点选择所需的版本,方法是调用

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。