【问题标题】:Template metaprogramming help: transforming a vector模板元编程帮助:转换向量
【发布时间】:2014-10-23 01:20:29
【问题描述】:

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

例如,我想要

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 对上述问题的回答)

我正在寻找简单的解释或指向解释正在发生的事情的文献。

这里有两个解决方案,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”、“&”和“*”的排列,但似乎没有任何帮助。)

我更喜欢v_transform_doesntwork的实现,而不是v_transform的实现,因为它更简单,但它有一个不工作的小问题。

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

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

【问题讨论】:

  • 闭包不是函数指针。它们可能对它们可转换,但这不是一回事。
  • 那为什么不转换呢?
  • 如果你可以依赖 C++11,看看 trailing-return-type 和 decltype。它们将使您的生活更轻松。
  • @kdog:要进行转换,您需要说明将其转换为哪种类型,但您没有这种情况,因为您选择要求模板参数推导。然后,参数推导也不会考虑用户定义的转换(如果您考虑一下,那将永远不会起作用)。

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


【解决方案1】:

您的 doesnotwork 需要一个函数指针和模式匹配。

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

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&lt;int, blah&gt;,其中blah 是一些分配器。

它看到第一个参数是std::vector&lt;T&gt;。它匹配Tint。由于您没有提供第二个参数,因此使用std::vector&lt;T&gt; 的默认分配器,它恰好匹配blah

然后我们继续第二个参数。您传入了一个闭包对象,因此它将(无法命名的)lambda 类型推断为Functor

模式匹配现在没有参数了。其余类型使用它们的默认类型——U 设置为typename std::result_of&lt;Functor(T)::type。这不会导致替换失败,因此不会发生 SFINAE。

所有类型都已确定,现在将函数放入重载集以检查以确定要调用的类型。由于没有其他同名函数,并且它是一个有效的重载,所以它被调用。


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

template<typename T, 
  typename A,
  typename Functor,
  typename U=typename std::decay<typename std::result_of<Functor&(T const&)>::type>::type
>
std::vector<U> v_transform(const std::vector<T, A> &v, Functor&& f){
  std::vector<U> ret;
  ret.reserve(v.size());
  for(const auto & e:v)
    ret.push_back(f(e));
  return ret;
}

涵盖了一些极端情况。

【讨论】:

  • 哇,这非常清晰和有用。你如何/在哪里学习这一切?似乎人们只是从以太中知道它。我不需要担心我的应用程序中的自定义分配,但你能解释一下在 result_of 的参数中将 Functor 更改为 Functor& 的意义何在?
  • @kdog 如果你将一个右值仿函数(就像你在 OP 中做的那样)传递给函数,那么 Functor 是类类型(没有引用),std::result_of&lt;Functor(T)&gt; 调用右值 @ 987654343@ 过载。但是,您可以在转换中的左值上下文中使用它。在极端的极端情况下,这将搞砸。哦,这让我想起了另一个问题——我们需要decay!我在哪里知道呢?十多年的 C++ 编码、使用该语言、阅读标准(简而言之!)、从各种来源学习等等。
  • @Yakk 哇——我绝对不会发现衰变虫……令人印象深刻!
  • 非常有趣,但我很好奇如果在 result_of 表达式中将 Functor& 更改为 Functor,会发生什么情况(a)失败; (b) 如果 std::decay 调用被省略,则失败?
  • @kdog 如果您的函数对象返回一个引用,您不能将引用存储在向量中,因此主体将失败。同样,如果一个函数返回一个const 对象。 decay 可用于将类型转换为“可存储”类型。 Functor&amp; 基本上需要一个具有不同 operator()&amp;&amp;operator()&amp; 重载的函数对象,此时这是一个荒谬的极端情况:此外(在所述衰减之后)它必须是一个非常奇怪的不同重载(@987654350 @返回一个引用,而operator()&amp;&amp;返回一个值,是......更常见)。
【解决方案2】:

问题 1

为什么对v_transform_doesntwork 的调用无法编译?

这是因为您向它传递了一个 C++11 lambda。 v_transform_doesntwork 中的模板参数是一个函数指针参数。实际上,C++11 lambda 是一种未知类型的对象。所以声明

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 设置为等于FunctorT 上被调用的resultstd::result_of 函数跳过了一些循环来确定返回值是什么。您可能还想将U 的定义更改为

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

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

【讨论】:

  • 为什么是 Functor&(T const &) 而不是 Functor(T const &) ?
  • 实际上坚持...我什至不明白为什么 Functor(T) 会产生与 Functor(T const &) 不同的结果。我的意思是,如果 Functor 参数被声明为 const T&,并且传入了 T 参数,那么调用仍将返回 U 类型。也许如果 Functor 被重载以执行不同的操作,如果它得到一个 const T& 而不是一个实际的 T调用站点的参数?
  • @kdog 是的,如果函数对象使用 TT const&amp; 参数做了不同的事情。
【解决方案3】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 2012-10-17
    • 2015-05-09
    相关资源
    最近更新 更多