【问题标题】:Is it possible to allow one std::function type accept lambdas with different signatures是否可以允许一种 std::function 类型接受具有不同签名的 lambda
【发布时间】:2014-08-25 01:23:42
【问题描述】:

我有一个高阶函数 map,它类似于 STL for_each,并将 std::function 对象映射到 vector 的事物上。

template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
  vector<U> ret;
  for (auto &x: xs)
    ret.push_back(f(x));
  return ret;
}

现在,我想让这个高阶函数同时获取 function&lt;int (const vector&lt;T&gt;&amp;)&gt;function&lt;int (vector&lt;T&gt;)&gt; 类型的对象,如所附的最小示例所示。

问题在于function&lt;int (const vector&lt;T&gt;&amp;)&gt;function&lt;int (vector&lt;T&gt;)&gt; 似乎可以相互转换(请参阅headhead2),但map 不会采用const 引用版本function&lt;int (const vector&lt;int&gt;&amp;)&gt;(请参阅@ 987654334@).

可以通过显式转换 (Q2) 告诉 map 接受 const 引用版本,但这相当麻烦。

我想知道,一般来说,是否可以编写一个函数 deref,从 function&lt;int (const vector&lt;T&gt;&amp;)&gt; 中删除 const 引用并返回 function&lt;int (vector&lt;T&gt;)&gt;

(如果以上是可能的,那么我将不必为 const refs 编写两个相同的 map 重载/实现)。

谢谢。

#include <vector>
#include <functional>
using namespace std;

template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
  vector<U> ret;
  for (auto &x: xs)
    ret.push_back(f(x));
  return ret;
}

int main() {
  vector<vector<int>> m;
  function<int (const vector<int>&)> head  =  [](const vector<int>& a) {return a[0];};
  function<int (const vector<int>&)> head1 =  [](vector<int> a) {return a[0];}; //conversion OK
  function<int (vector<int>)> head2 =  [](const vector<int>& a) {return a[0];}; //conversion OK
  map(head2,m); //OK

  map(head,m); //Q1: problem line, implicit conversion NOT OK
  map(function<int (vector<int>)>(head),m); //Q2: explicit conversion OK
  map(deref(head),m); //Q3: ??How-to, deref takes a std::function f and returns a function with const ref removed from its signature

  return 0;
}

--- 编辑 ---

我对 deref 类似的函数或元函数特别感兴趣,它可以从 std::function 对象的类型签名中删除 const ref,这样我至少可以自动执行 Q2

我知道,正如@Brian 和@Manu 正确指出的那样,使用std::function 来指定类型不是常规的,但我想知道我上面问的是否可行。 个人,我认为std::function 的代码更清晰,考虑到泛型函数类型Func&lt;T1, T2, T3, ...,Tn, Tresult&gt; 在C# 中是如何使用的。这是如果类型擦除的成本是可以容忍的。

我完全同意 c++ 可以推断返回类型并在类型错误时给出错误消息。也许这只是一个口味问题,我更愿意在编写函数签名时把它拼出来。

【问题讨论】:

  • 不要使用std::function作为map函数参数。只使用一个模板。这减少了您正在试验的耦合,并提高了性能,允许编译器进行内联。

标签: c++ c++11 std-function


【解决方案1】:

我明白你为什么使用std::function:你必须知道转换的返回类型才能创建向量,对吧?

但请考虑一种完全不同的方法。给定元函数std::result_of,您可以计算函数调用的结果类型,因此只需编写:

template<typename F , typename CONTAINER , typename T = typename std::result_of<F(typename CONTAINER::value_type)>::type>
std::vector<T> map( F f , CONTAINER&& container )
{
    std::vector<T> result;

    for( auto& e : container )
        result.emplace_back( f( e ) );

    return result;
}

优点:

  • 不要滥用std::function:总是想std::function做了什么(即类型擦除),不要将它用作通用函数类型。

    李>
  • 依赖鸭子类型而不是类型上的耦合:别担心,如果出现问题,它也不会编译。

  • 适用于任何标准库容器,因为我们使用 value_type 特征提取元素类型,而不是直接使用 std::vector

  • 代码更加清晰和高效,这都是因为减少了std::function 的使用。

关于问题“是否可以编写一个接受多个签名的 lambdas 的函数?

使用std::function,您可以用几行代码编写类似于 Boost.OverloadedFunction 的内容:

template<typename F , typename... Fs>
struct overloaded_function : public std_function<F> , public std_function<Fs>...
{
    overloaded_function( F&& f , Fs&&... fs ) :
        std_function<F>{ f },
        std_function<Fs>{ fs }...
    {}
};

其中std_function 是一个元函数,它给定函数类型F 返回带有F 签名的std::function 实例。我把它留给读者作为游戏/挑战。

仅此而已。使用类似 make 的功能对其进行改进:

template<typename F , typename... Fs>
overloaded_function<F,Fs...> make_overloaded_function( F&& f , Fs&&... fs )
{
    return { std::forward<F>( f ) , std::forward<Fs>( fs )... };
}

你准备好了:

auto f = make_overloaded_function( [](){ return 1; } ,
                                   [](int,int){ return 2; } ,
                                   [](const char*){ return 3; } );

f();        //Returns 1
f(1,2);     //Returns 2
f("hello"); //Returns 3

编辑:“谢谢。但是,我真正想要的是一个元函数,它接受可调用的签名,并从签名中删除 const refs。

好的,让我试试:std::decay 元函数在将参数按值传递给给定类型时应用衰减完成。这包括删除 cv 限定符、删除引用等。因此,像您这样的元函数可能是采用函数签名类型并将衰减应用于其所有参数的东西:

template<typename F>
struct function_decay;

template<typename R typename... ARGS>
struct function_decay<R(ARGS...)>
{
    using type = R(typename std::decay<ARGS>::type...);
};

这应该可以完成工作。

我写这个是因为你在评论中明确要求它,但我强烈建议你使用我最初向你展示的替代方案,因为与你的方式相比,它有很多优点。
也就是说,我希望这个答案有助于解决您的问题。

【讨论】:

  • 请注意,他有时需要可变输入,所以我会将 const CONTAINER&amp; 更改为 CONTAINER&amp;&amp;。也把F当成F&amp;&amp;,不是为了转发,而是作为一个完美的参考。
  • @Manu343726 谢谢。但是,我真正在寻找的是一个元函数,它采用可调用的签名,并从签名中删除 const refs。这就是 OP 中的“deref”部分。
  • @Manu343726 感谢您的建议,我会牢记这一点。我将尝试使用您的function_decay 元函数,并找出练习中的std_function 部分。
  • @Manu343726 请你发一下std_function应该是什么样子的?
【解决方案2】:

惯用的解决方案是简单地允许map 采用任意类似函数的类型,

template<class T, class F>
auto map(F f, vector<T> xs) -> vector<typename result_of<F(T)>::type> {
  vector<typename result_of<F(T)>::type> ret;
  for (auto &x: xs)
    ret.push_back(f(x));
  return ret;
}

这种方法的主要问题是,如果F 不能使用T 类型的参数调用,或者如果它返回一些奇怪的东西,比如void,你会得到令人困惑的错误消息。

(第二个问题是map 的第一个参数不能是重载函数;编译器不能简单地选择采用T 类型参数的重载。)

(您可能还需要考虑衰减f 的返回类型。)

【讨论】:

  • +1 比我早五秒,Nooooooo,你打败了我;)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-16
相关资源
最近更新 更多