模板元编程帮助:转换矢量

时间:2014-10-23 01:20:30

标签: c++ templates c++11 template-meta-programming

作为我的第一个模板元程序,我正在尝试编写一个将输入向量转换为输出向量的函数。

例如,我想要

vector<int> v={1,2,3};
auto w=v_transform(v,[](int x){return (float)(x*2)})

将w设置为三个浮点数的向量{2.0, 4.0, 6.0}

我从这个stackoverflow问题The std::transform-like function that returns transformed container开始,它解决了转换任意容器这个更难的问题。

我现在有两个解决方案:

  1. 一个解决方案,v_transform_doesntwork不起作用,但我不知道为什么(我自己写的)。

  2. 一个解决方案,v_transform有效,但我不知道为什么(基于Michael Urman对上述问题的回答)

  3. 我正在寻找解释正在发生的事情的文学的简单解释或指示。

    以下是两个解决方案,v_transform_doesntworkv_transform

    #include <type_traits>
    #include <vector>
    
    using namespace std;
    
    template<typename T, typename Functor,
        typename U=typename std::result_of<Functor(T)>::type>
    vector<U> v_transform(const std::vector<T> &v, Functor&& f){
        vector<U>ret;
        for(const auto & e:v)
            ret.push_back(f(e));
        return ret;
    }
    
    template<typename T, typename U> 
    vector<U> v_transform_doesntwork(const std::vector<T> &v, U(*f)(const T &)){
        vector<U>ret;
        for(const auto & e:v)
            ret.push_back(f(e));
        return ret;
    }
    
    float foo(const int & i){
        return (float)(i+1);
    }
    
    int main(){
        vector<int>v{1,2,3,4,5};
        auto w=v_transform(v,foo);
        auto z=v_transform(v,[](const int &x){return (float)(x*2);});
        auto zz=v_transform(v,[](int x){return (float)(x*3);});
        auto zzz=v_transform_doesntwork(v,[](const int &x){return (float)(x*2);});
    }
    

    问题1 :为什么不调用v_transform_doesntwork? (它给出了一个无法匹配的模板错误,c ++ 11.我在参数列表中尝试了大约4个“const”和“&amp;”和“*”的排列,但似乎没有任何帮助。)

    我更喜欢v_transform_doesntworkv_transform的实现,因为它更简单,但它有一个不起作用的轻微问题。

    问题2 :为什么对v_transform的调用有效?我明确了解发生了什么,但我不明白为什么在定义U时需要所有类型名称,我不明白定义一个模板参数的奇怪语法如何在后面的同一个定义中依赖甚至允许,或者这都是指定的。我尝试在cppreference中查找“依赖类型名称”,但没有看到这种语法。

    进一步说明:我假设v_transform有效,因为它编译。如果在某些情况下它会失败或出现意外行为,请告诉我。

3 个答案:

答案 0 :(得分:4)

您的doesnotwork期望函数指针和模式匹配。

lambda不是函数指针。无状态lambda可以转换为函数指针,但模板模式匹配不使用转换(除了非常有限的子集 - Derived&Base&Derived*到{{1} },引用值,反之亦然等 - 绝不是构造函数或转换运算符。

Base&传递给foo,它应该有效,除非您的代码中存在拼写错误。

doesnotwork

所以你打电话给template<typename T, typename Functor, typename U=typename std::result_of<Functor(T)>::type > vector<U> v_transform(const std::vector<T> &v, Functor&& f){ vector<U>ret; for(const auto & e:v) ret.push_back(f(e)); return ret; } 。它试图推断出模板类型。

它与第一个参数匹配。您传递v_transform std::vector<int, blah>是一些分配器。

它看到第一个参数是blah。它将std::vector<T>T匹配。由于您没有提供第二个参数,因此使用了int的默认分配器,该匹配器恰好与std::vector<T>匹配。

然后我们继续第二个参数。您传入了一个闭包对象,因此它将(不可约)lambda类型推导为blah

现在出现了模式匹配的参数。其余类型使用其默认类型 - Functor设置为U。这不会导致替换失败,因此不会发生SFINAE。

确定所有类型,现在将函数插入到重载集中以检查以确定要调用的内容。因为没有其他同名函数,并且它是一个有效的重载,所以它被调用。


请注意,您的代码有一些小错误:

typename std::result_of<Functor(T)::type

涵盖了一些角落案件。

答案 1 :(得分:2)

问题1

为什么不调用v_transform_doesntwork编译?

这是因为你已经传递了一个C ++ 11 lambda。 v_transform_doesntwork中的模板参数是函数指针参数。事实上,C ++ 11 lambdas是未知类型的对象。声明

template<typename T, typename U>
vector<U> v_transform_doesntwork(const std::vector<T> &v, U(*f)(const T &))

将T绑定到函数指针f的输入类型,并将U绑定到函数指针的输出类型。但是第二个论点因此不能接受lambda!您可以明确指定类型以使其与非捕获 lambda一起使用,但编译器不会在强制转换时尝试类型推断。

问题2

为什么对v_transform的调用有效?

让我们看看你写的代码:

template<typename T, 
typename Functor,
typename U=typename std::result_of<Functor(T)>::type>
vector<U> v_transform(const std::vector<T> &v, Functor&& f){

同样,T是表示输入类型的模板参数。但是现在Functor是您决定传入v_transform的任何可调用对象的参数(名称没什么特别之处)。我们将U设置为等于Functor上调用的T结果std::result_of函数跳过一些箍来弄清楚返回值是什么。您还可能希望将U的定义更改为

typename U=typename std::result_of<Functor&(T const &)>::type>

因此可以接受将常量或引用作为参数的函数。

答案 2 :(得分:0)

对于doesntwork函数,您需要显式指定模板参数:

auto zzz=v_transform_doesntwork<int,float>(v,[](const int &x){return (float)(x*2);});

然后它确实有效。编译器在将lambda转换为函数指针时无法隐式确定这些参数。