函数采用lambda表达式

时间:2013-07-18 10:20:27

标签: c++11 lambda

出于练习目的,我想创建一个类似于std::transform()的函数:

template<class Tin, class Tout>
std::vector<Tout> map( const std::vector<Tin>& in,
                       const std::function<Tout(const Tin&)>& mapper ) {

   std::vector<Tout> ret;
   for( auto elem : in ) {
       ret.push_back( mapper( in ) );
   }

   return ret;
}

我打算按如下方式使用它:

std::vector<Bar> bars /* = ... */;
std::vector<Foo> foos = map( bars, []( const Bar& bar ) { return bar.to_foo(); } );

但是,我得到函数调用的未定义引用。我map()函数的正确签名是什么?

* 更新:* 以下是实际的错误消息(Bar = std::stringFoo = IPv6(自有类))

config.cc:98:61: error: no matching function for call to ‘map(const std::vector<IPv6>&, InterfaceConfig::set_ip6(const std::vector<IPv6>&)::<lambda(const IPv6&)>)’
config.cc:98:61: note: candidate is:
utils.h:38:31: note: template<class Tin, class Tout> std::vector<Tout> utils::map(const std::vector<Tin>&, const std::function<Tout(const Tin&)>&)

这是电话:     std :: vector strings = utils :: map(ips,             [](const IPv6&amp; ip){return ip.to_string(); });

2 个答案:

答案 0 :(得分:4)

您的代码中有两件事情无效。

  • 首先,当传递lambda函数作为参数时,我建议使用Template。例如,Microsoft上的标准库似乎将此方法用于std::for_each
  • 并且:

      

    当函数模板具有无法从参数推导出的返回类型时,或者当函数模板没有任何参数时,编译器无法推断出该类型。此函数将需要模板类型参数规范

看一下这个例子:

template<class Tout, class Tin, class Fun>
//                            ^^^^^^^^^^^
// Note that I changed the order of the types
std::vector<Tout> map( const std::vector<Tin>& in,
                       Fun mapper ) {
//                     ^^^^^^^^^^
   std::vector<Tout> ret;
   for( auto elem : in ) {
       ret.push_back( mapper( elem ) );
   }

   return ret;
}

int main()
{
    std::vector<int> bars /* = ... */;
    std::vector<float> foos = map<float>( bars, []( int ) { return 1.0f; } );
    //                           ^^^^^^^ Specify the type Tout
    system( "pause" );
    return 0;
}

编辑:

就像在评论中说的那样,我们可以使用decltypestd::decay来明确指定函数的结果:

  template<class Tin, class Fun> // no Tout
//                  ^^^^^^^^^^^
  auto map( const std::vector<Tin>& in, Fun mapper )
//^^^^                                  ^^^^^^^^^^
    -> std::vector<typename std::decay< decltype( mapper( in.front() ) )>::type > {

   std::vector<typename std::decay< decltype( mapper( in.front() ) )>::type > ret;


   for( auto elem : in ) {
       ret.push_back( mapper( elem ) );
   }

   return ret;
}

int main()
{
    std::vector<int> bars /* = ... */;
    std::vector<float> foos = map( bars, []( int ) { return 1.0f; } );
    //                           No specification
    system( "pause" );
    return 0;
}

让我们解释一下。

首先,我们将使用后期指定的返回类型语法。它允许我们在返回类型规范中使用参数名称。我们使用auto开始这一行,并使用->将返回类型规范放在参数之后。

我们将使用decltype因为decltype类型说明符产生指定表达式的类型。在我们的案例中,这将非常有用。例如,要获取我们在参数中传递的函数的类型,它只是decltype( f( someArg ) )

让我们说明我们想要什么:函数的返回类型应该是参数右边传递的函数的返回类型的向量?所以我们可以返回std::vector< decltype( mapper( in.front() ) )>就是这样! (为什么in.front()?我们必须将参数传递给函数以获得有效的表达式。)

但是在这里,我们遇到了一个问题:std::vector不允许引用。为了确保它对我们来说不是问题,我们将使用{em>应用左值到右值,数组到指针和函数到指针隐式转换的std::decay元函数。到类型T,删除cv-qualifiers 删除引用,并将结果类型定义为成员typedef类型。。也就是说,如果函数返回类似const Foo&的函数,它将以Foo结束。

所有这一切的结果:std::vector< typename std::decay< decltype( mapper( in.front() ) )>::type >

你必须在函数的开头再次重复这个表达式,以声明你将返回的变量。

一些有用的参考资料:

解释起来并不容易,我希望我的解释是可以理解的。

答案 1 :(得分:0)

无需明确指定地图的结果,可以推导出。我也将接受任何范围(提供开始和结束的东西),仅仅因为这样做是微不足道的。我可以使它更通用,并使用免费的开始和结束版本,但这使它更复杂,所以我不会。

template <typename Range, typename Func>
auto map(const Range& r, Func f)
    -> std::vector<typename std::decay<decltype(f(*r.begin()))>::type> {
  std::vector<typename std::decay<decltype(f(*r.begin()))>::type> result;
  for (const auto& e : r) {
    result.push_back(f(e));
  }
  // Alternatively:
  //std::transform(r.begin(), r.end(), std::back_inserter(result), f);
  return result;  
}

这不是简单的代码,所以让我解释一下。

首先,我在这里使用后期指定的返回类型语法:我使用auto启动函数,并将实际的返回类型放在参数之后,用->表示。这允许我在返回类型规范中使用参数名称,这在我接下来要做的decltype内容中非常有用。

那么我们真正想要的是什么?当使用f元素调用时,我们需要一个r返回的向量。那是什么?好吧,我们可以使用decltype来查找。 decltype(expr)为您提供expr的类型。在这种情况下,表达式是对fdecltype(f(arguments))的调用。我们有一个参数:范围的一个元素。范围为我们提供的唯一内容是begin()end(),所以让我们使用:解引用begin()来获取实际值。表达式现在为decltype(f(*r.begin()))。请注意,这实际上从未进行过评估,因此范围是否为空无关紧要。

好的,这给了我们函数的返回类型。但是如果我们写std::vector<decltype(...)>给我们留下一个问题:函数的返回类型可能是一个引用,但引用的向量是无效的。所以我们将std::decay元函数应用于返回类型,它会删除引用类型上的引用和cv限定符,因此如果函数返回const Foo&,则std::decay的结果只是{{ 1}}。

这使我得到了最终的返回类型Foo

然后你重复同样的事情来声明保存返回值的实际变量。不幸的是,因为没有办法在任何合理的点上放置类型别名,所以你无法摆脱它。


无论如何,原始代码还有另一个问题,那就是将循环变量声明为std::vector<typename std::decay<decltype(f(*r.begin()))>::type>。如果您使用auto来调用此map,则会以循环结束

vector<Bar>

注意你的循环变量是一个值?这意味着您将for (Bar b : in) { ... } 的每个元素复制到局部变量。如果in复制费用昂贵,则这是一个严重的性能问题。如果您的转换依赖于其参数的对象标识(例如,您返回指向成员的指针),那么您的性能问题已成为正确性问题,因为生成的向量充满了悬空指针。这就是你应该在循环中使用Bar的原因,或者只是在内部使用const auto&算法,这是正确的。