是否可以允许一个std :: function类型接受具有不同签名的lambdas

时间:2014-07-03 19:25:33

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

我有一个更高阶的函数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<int (const vector<T>&)>function<int (vector<T>)>类型的对象,如附加的最小示例所示。

问题在于function<int (const vector<T>&)>function<int (vector<T>)>似乎可以相互转换(请参阅headhead2),但map不会const引用版本function<int (const vector<int>&)>(参见Q1)。

可以告诉map接受带有显式转换(Q2)的const引用版本,但这很麻烦。

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

(如果可以,那么我将不必为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,这样我至少可以{{1自动。

我知道,正如@Brian和@Manu正确指出的那样,使用Q2指定类型并不常见,但我想知道我上面提到的内容是否可行。 就个人而言,我认为std::function的代码更清晰,考虑到在C#中如何使用泛型函数类型std::function。如果类型擦除的成本是可以容忍的话。

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

2 个答案:

答案 0 :(得分:21)

我理解你使用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使用量减少了。

关于“可以写一个接受多个签名的lambda的函数吗?

使用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的元函数,返回std::function实例,其签名为F。我将其作为游戏/挑战留给读者。

多数民众赞成。使用类似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引号。

好的,让我试试:std::decay元函数应用在按值传递argumments时所做的衰减到给定类型。这包括删除cv限定符,删除引用等等。所以像你这样的元函数可能是一个采用函数签名类型并将衰减应用于其所有argumments的东西:

template<typename F>
struct function_decay;

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

那应该做的工作。

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

答案 1 :(得分:4)

惯用解决方案是简单地允许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),则会出现令人困惑的错误消息。 / p>

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

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