我一直在尝试在C ++ 11中实现Python的函数map。它似乎适用于任何类型的可调用对象,但如果我希望它与函数模板一起使用,我必须指定模板类型参数。例如:
#include <iostream>
#include <list>
template<typename T>
T abs(T x)
{
return x < 0 ? -x : x;
}
int main()
{
std::list<int> li = { -1, -2, -3, -4, -5 };
for (auto i: map(&abs<int>, li))
{
std::cout << i << std::endl;
}
}
它工作正常,但我希望它从函数的第二个参数中推导出int
参数,因此能够写出:
for (auto i: map(&abs, li))
{
std::cout << i << std::endl;
}
我的map
函数写为:
template<typename Callable, typename Container>
auto map(const Callable& function, Container&& iter)
-> MapObject<Callable, Container>
{
return { function, std::forward<Container>(iter) };
}
其中MapObject
是实施的一部分,而不是真正的问题。如何更改其定义,以便可以从Callable
对象推导出Container
对象的模板类型?例如,map
如何知道在给出abs<int>
时我们必须对给定abs
使用list<int>
?
答案 0 :(得分:5)
它工作正常,但我希望它从函数的第二个参数中推导出int参数,因此能够写出:
for (auto i: map(&abs, li))
{
std::cout << i << std::endl;
}
问题是abs
不是函数,而是函数模板,因此没有地址 - abs
,尽管有&abs<int>
因为abs<int>
(特化)确实是一个函数(从模板生成)。
现在问题是你真正想要解决的问题,特别是你必须意识到C ++是一种静态类型语言,其中python是一种动态类型语言。我不清楚你在不同层面上想要达到的目标。例如,python中的函数map
在C ++中的std::transform
中具有等价:
a = [ 1, 2, 3 ]
a = map(lambda x: 2*x, a)
std::vector<int> v{1,2,3};
std::transform(v.begin(),v.end(),v.begin(),[](int x){ return 2*x; });
我稍微有些作弊,因为在python中它会创建一个不同的容器但是在C ++中transform
在迭代器级别工作并且知道没有容器,但是你可以获得相同的效果:
std::vector<int> v{1,2,3};
std::vector<int> result;
// optionally: result.reserve(v.size());
std::transform(v.begin(),v.end(),
std::back_inserter(result),
[](int x) { return 2*x; });
我建议你学习语言中的习语,而不是试图用其他语言来实现习语......
顺便说一句,如果你愿意让用户指定传递给map
函数的仿函数的类型,那么你可以只传递模板的名称并让编译器找出你的专业化需要:
template <typename Container>
auto map(Container && c,
typename Container::value_type (*f)(typename Container::value_type))
-> MapObject<Callable<T>,Container>;
template <typename T>
T abs(T value);
int main() {
std::vector<int> v{1,2,3,4};
map(v,abs);
}
这不像你想要做的那样通用,因为它只接受函数指针和具体类型(这甚至不如std::transform
更通用)并且它在编译器看到{{1}时起作用(没有abs
)它会将其解析为模板,从而解析为特殊化集。然后它将使用期望的类型来选择一个特化并将其传入。在这种情况下,编译器将隐式为您执行&
。
另一种更通用的替代方法是不使用函数,而是使用函子。考虑到这一点,您可以将&abs<int>
定义为:
abs
然后传递函子的副本而不是函数指针。在将对象struct abs {
template <typename T>
T operator()(T t) { ...}
};
传递到abs
函数的情况下,只有在使用它时才需要确定要使用的重载。来电方面看起来像:
map
额外的括号组正在创建for (auto& element : map(container,abs()))
类型的对象并将其传入。
总的来说,我会尽量避免这种情况。这是一件有趣的事情,你可以找到一个很好的解决方案,但它将 hard 并且需要相当多的c ++专业知识。由于语言不支持它,因此您必须设计一些在语言中有效的东西,并且需要对不同的功能或语法进行折衷。了解选项本身就是一个难题,更难理解妥协,更难以获得良好的解决方案。 好的解决方案可能会比同等的惯用C ++代码更差。
如果使用C ++编程,请编写C ++程序。尝试通过C ++编译器对 python 进行编码可能会让您感受到C ++的痛苦和python的性能。
答案 1 :(得分:-1)
它没有推断它,因为您从未指定Callable是模板。你使Callable成为模板模板参数,它应该为你推断出它的类型。
template<template <typename T> typename Callable, typename Container>
auto map(const Callable<T>& function, Container&& iter)
-> MapObject<Callable<T>, Container>
{
return { function, std::forward<Container>(iter) };
}
你可能会被咬住,因为你仍然无法将模板的地址实例化。不知道为什么你需要地址 - 但是......