功能模板作为参数

时间:2013-04-27 19:19:55

标签: c++ templates c++11 function-pointers

我一直在尝试在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>

2 个答案:

答案 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) };
}

你可能会被咬住,因为你仍然无法将模板的地址实例化。不知道为什么你需要地址 - 但是......