出于练习目的,我想创建一个类似于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::string
,Foo = 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(); });
答案 0 :(得分:4)
您的代码中有两件事情无效。
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
开始这一行,并使用->
将返回类型规范放在参数之后。
我们将使用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
的类型。在这种情况下,表达式是对f
:decltype(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&
算法,这是正确的。