编写一个函数,该函数接受一个lambda表达式作为参数。

44
我有一个这样的方法
template<typename T, typename U>
map<T,U> mapMapValues(map<T,U> old, T (f)(T,U))
{
    map<T,U> new;
    for(auto it = old.begin(); it != old.end(); ++it)
    {
        new[it->first] = f(it->first,it->second);
    }
    return new; 
}

这个想法是你会像这样调用它

BOOST_AUTO_TEST_CASE(MapMapValues_basic)
{
    map<int,int> test;
    test[1] = 1;
    map<int,int> transformedMap = VlcFunctional::mapMapValues(test, 
        [&](int key, int value) -> int
        {
            return key + 1; 
        }
    );
}

然而我收到了错误信息:没有匹配参数列表的函数模板实例 "VlcFunctional::mapMapValues",参数类型为:(std::map, std::allocator>>, __lambda1)。

你有什么想法我做错了什么吗?我使用的是Visual Studio 2008和Intel C++编译器11.1。


"new"是一个关键字,你不能定义一个名为"new"的变量。 - Motti
1
哈哈。非常敏锐,因为代码没有找到正确的模板参数,所以它实际上从未被编译,因此编译器从未向我指出这一点 :) - Jamie Cook
我不确定 lambda 函数是否需要 "-> int"。 - serup
5个回答

48

你的函数需要一个函数指针,而不是lambda表达式。

C++中,一般有三种“可调用对象”:

  1. 函数指针。
  2. 函数对象。
  3. Lambda函数。

如果你想在函数接口中使用所有这些内容,那么可以使用std::function

template<typename T, typename U> 
map<T,U> mapMapValues(map<T,U> old, std::function<T(T, U)> f)
{
    ...
}

这将允许使用上述三种可调用对象类型之一来调用函数。然而,这种方便的代价是在函数调用时会有少量开销(通常是空指针检查,然后通过函数指针调用)。这意味着该函数几乎肯定不会被内联(除非使用高级WPO/LTO)。
或者,您可以添加一个额外的模板参数以接受第二个参数的任意类型。这将更有效率,但您会失去对所使用函数的类型安全,并可能导致更多的代码膨胀。
template<typename T, typename U, typename F> 
map<T,U> mapMapValues(map<T,U> old, F f) 

@matthieu,你指的是哪些实用程序?我原本认为boost::function应该可以主要达到与@peter建议的std::function相同的效果,但它在声明中具有与使用函数指针相同的问题。 - Jamie Cook
7
使用 std::function 需要添加头文件 #include <functional> - Peter Alexander
@Peter,std::function是在c++0x编译器的头文件中引入的,而Intel c++ 11.1虽然是兼容c++0x的编译器,但它并不配备头文件,你需要从安装的开发环境中获取,例如我使用的MSVS 2008,并不支持c++0x,也没有在<functional>中定义std :: function。因此,我想在升级到Visual Studio 2010之前,我只能使用模板解决方案。 - Jamie Cook
1
@Jamie:我之前认为你使用了一个C++0x感知的编译器,因为你在原始帖子中使用了lambda函数。对此很抱歉。 - Peter Alexander
1
Intel C++是c++0x感知的...你只需要提供它c++0x库,它就会感知到 :) - Jamie Cook
显示剩余5条评论

13
你的参数类型声明T(f)(T,U)属于“自由函数,接受一个T和一个U并返回一个T”类型。你不能传递一个lambda表达式、函数对象或任何除了符合该签名的实际函数之外的东西。
你可以通过将参数类型更改为std::function<T(T,U)>来解决这个问题,像这样:
template<typename T, typename U> 
map<T,U> mapMapValues(map<T,U> old, std::function<T(T,U)>)
{
}

或者,您可以像这样将函数类型声明为模板参数:

template<typename T, typename U, typename Fn> 
map<T,U> mapMapValues(map<T,U> old, Fn fn)
{
  fn(...);
}

谢谢Joe,我选择了Peter的答案,只是因为他比你快了一分钟。还是谢谢你。 - Jamie Cook

11

我想提供这个简单但自我解释的示例。它展示了如何将"可调用的东西"(函数,函数对象和lambda表达式)传递给函数或对象。

// g++ -std=c++11 thisFile.cpp

#include <iostream>
#include <thread>

using namespace std;

// -----------------------------------------------------------------
class Box {
public:
  function<void(string)> theFunction; 
  bool funValid;

  Box () : funValid (false) { }

  void setFun (function<void(string)> f) {
    theFunction = f;
    funValid = true;
  }

  void callIt () {
    if ( ! funValid ) return;
    theFunction (" hello from Box ");
  }
}; // class

// -----------------------------------------------------------------
class FunClass {
public:
  string msg;
  FunClass (string m) :  msg (m) { }
  void operator() (string s) {
    cout << msg <<  s << endl; 
  }
};

// -----------------------------------------------------------------
void f (string s) {
  cout << s << endl;
} // ()

// -----------------------------------------------------------------
void call_it ( void (*pf) (string) ) {
  pf( "call_it: hello");
} // ()

// -----------------------------------------------------------------
void call_it1 ( function<void(string)> pf ) {
  pf( "call_it1: hello");
} // ()

// -----------------------------------------------------------------
int main() {

  int a = 1234;

  FunClass fc ( " christmas ");

  f("hello");

  call_it ( f );

  call_it1 ( f );

  // conversion ERROR: call_it ( [&] (string s) -> void { cout << s << a << endl; } );

  call_it1 ( [&] (string s) -> void { cout << s << a << endl; } );

  Box ca;

  ca.callIt ();

  ca.setFun (f);

  ca.callIt ();

  ca.setFun ( [&] (string s) -> void { cout << s << a << endl; } );

  ca.callIt ();

  ca.setFun (fc);

  ca.callIt ();

} // ()

非模板具体类型正是我所需要看到的。 - Niko
函数<T>重载运算符(bool),因此您可以写if(theFunction)而不是if(!funValid)。 - Xeenych Xeenych

6
根据n3052,带有空捕获列表的Lambda表达式应该退化为函数指针。然而,似乎VC++没有实现这个功能,g++只实现了部分功能,请参考我的SO question

不支持在Intel C++ 11.1中使用。 - Jamie Cook

3
这里有一些关于如何将函数作为参数传递的示例。
class YourClass
{
void YourClass::callback(void(*fptr)(int p1, int p2))
{
    if(fptr != NULL)
      fptr(p1, p2);
}
};

void dummyfunction(int p1, int p2)
{
   cout << "inside dummyfunction " << endl;
}

YourClass yc;

// using a dummyfunction as callback
yc.callback(&dummyfunction);

// using a lambda as callback
yc.callback( [&](int p1, int p2) { cout << "inside lambda callback function" << endl; } );

// using a static member function 
yc.callback( &aClass::memberfunction );

那个lambda示例非常误导人,因为一旦你尝试从封闭作用域中捕获任何东西,它就无法编译。如果没有任何捕获,它会隐式地将lambda转换为函数指针,但是如果它捕获了任何东西,它就无法这样做。换句话说,在参数列表前面的[&]是完全无用的。 - dannymcgee
@dannymcgee 抱歉,我不明白你在这里说什么。 - serup
抱歉回复晚了!C++ lambda表达式以方括号中的捕获列表开始。您可以使用它来指定来自封闭范围(即包含lambda的范围)的变量,这些变量可以在lambda的主体内使用。您可以显式地指定捕获(例如,[foo, &bar]()-> void {std :: cout << foo << bar;}将按值捕获foo和按引用捕获bar),或者隐式地指定(例如,[&]()-> void {std :: cout << foo << bar;}将按引用捕获foobar[] 显式地不捕获任何内容,这相当于一个cdecl函数。(...) - dannymcgee
然而,由于您的lambda示例使用了一个隐式引用捕获列表([&]),所以它只与类型void (*)(int, int)兼容是巧合,因为该lambda在其主体内不尝试引用来自封闭作用域的任何变量。如果您将lambda主体更改为例如 cout << "Heres my address: " << &yc << endl;,则会收到令人困惑的编译器错误,指出您已将错误类型传递给YourClass::callback--因为它现在隐式捕获了yc,所以它不能再被视为简单的cdecl函数指针。 - dannymcgee
如果你使用了一个空的捕获列表([](int p1, int p2) { ... }),那么完全无法捕获yc,因为它是显式非捕获的lambda函数。 - dannymcgee
1
这听起来可能有点过于追求完美了——为了背景,我在阅读你的答案后尝试将一个“捕获”lambda表达式传递给函数指针,但我无法弄清楚为什么它不起作用,因为你的示例使用了[&]捕获列表。花了我一分钟才注意到你实际上并没有“使用”任何捕获。所以我很沮丧地匆忙写了一条评论,这可能是我不应该做的事情。 :)简而言之:为了清晰起见,我认为你应该更新你的lambda示例,使其具有空的捕获列表:[](int p1, int p2) { ... } - dannymcgee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接