1

出于练习目的,我想创建一个类似于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& ip ) { return ip.to_string(); } );

4

2 回答 2

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;
}

编辑 :

就像评论中所说的那样,我们可以使用decltype并且std::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并将返回类型规范放在参数之后->

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

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

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

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

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

一些有用的参考资料:

不太容易解释,希望我的解释可以理解。

于 2013-07-18T10:46:00.367 回答
0

map的结果不需要明确指定,可以推导出来。我还将接受任何范围(提供开始和结束的东西),仅仅是因为这样做很简单。我可以使它更通用并使用免费的开始和结束版本,但这使它变得更加复杂,所以我不会。

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。在这种情况下,表达式是对f:的调用decltype(f(arguments))。我们有一个论点:范围的一个元素。范围给我们的唯一东西是begin()and end(),所以让我们使用它:取消引用begin()来获取实际值。现在的表达是decltype(f(*r.begin()))。请注意,这从未实际评估过,因此范围是否为空无关紧要。

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

这给我留下了最终的返回类型std::vector<typename std::decay<decltype(f(*r.begin()))>::type>

然后你可以重复同样的事情来声明保存返回值的实际变量。不幸的是,由于无法将类型别名放在任何合理的位置,因此您无法摆脱这一点。


无论如何,您的原始代码还有另一个问题,那就是将循环变量声明为auto. 如果你map用 a调用它vector<Bar>,你最终会得到循环

for (Bar b : in) { ... }

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

于 2013-07-18T12:44:20.567 回答