【问题标题】:How to deduce the return type of a function object from parameters list?如何从参数列表中推断出函数对象的返回类型?
【发布时间】:2014-12-10 14:05:45
【问题描述】:

我正在尝试编写一个可以将vector<T> 转换为vector<R> 的投影函数。这是一个例子:

auto v = std::vector<int> {1, 2, 3, 4};
auto r1 = select(v, [](int e){return e*e; }); // {1, 4, 9, 16}
auto r2 = select(v, [](int e){return std::to_string(e); }); // {"1", "2", "3", "4"}

第一次尝试:

template<typename T, typename R>
std::vector<R> select(std::vector<T> const & c, std::function<R(T)> s)
{
   std::vector<R> v;
   std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
   return v;
}

但是对于

auto r1 = select(v, [](int e){return e*e; });

我明白了:

错误 C2660: 'select' : 函数不接受 2 个参数

我必须明确调用select&lt;int,int&gt; 才能工作。我不喜欢这样,因为类型是多余的。

auto r1 = select<int, int>(v, [](int e){return e*e; }); // OK

第二次尝试:

template<typename T, typename R, typename Selector>
std::vector<R> select(std::vector<T> const & c, Selector s)
{
   std::vector<R> v;
   std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
   return v;
}

结果是同样的错误,函数不接受 2 个参数。在这种情况下,我实际上必须提供第三种类型的参数:

auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });

第三次尝试:

template<typename T, typename R, template<typename, typename> class Selector>
std::vector<R> select(std::vector<T> const & c, Selector<T,R> s)
{
   std::vector<R> v;
   std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
   return v;
}

对于

auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });

错误是:

'select' : 'Selector' 的模板参数无效,需要类模板

对于

auto r1 = select(v, [](int e){return e*e; });

错误 C2660: 'select' : 函数不接受 2 个参数

(我知道最后两次尝试不是特别好。)

我如何编写这个select() 模板函数来为我在开头放置的示例代码工作?

【问题讨论】:

  • 请问您使用的是什么编译器?因为对我来说(使用你的第一次尝试)_select(v, [](int e){return (e);}) 不起作用,因为从 lambda -> std::function 无法直接完成转换......
  • VC++ 2013(我忘了标记)。
  • @CollioTV 为什么不能进行这种演员阵容?这里的问题是类型推导和类型转换不起作用。
  • @leemes 因为无法推导出模板...但是 Piotr S. 找到了一个很好的解决方法!
  • @CollioTV 你说从 lambda 到 std::function 的转换不能直接完成。这是错误的。在这种情况下, 转换 类型推断不能同时进行。但这并不特定于 lambda 或 std::function。这是函数参数的一个普遍问题,其类型涉及模板参数+传递的对象必须首先转换以匹配参数类型“模式”。

标签: c++ templates visual-c++ projection visual-c++-2013


【解决方案1】:

选项#1:

基本decltype()用法:

template <typename T, typename F>
auto select(const std::vector<T>& c, F f)
    -> std::vector<decltype(f(c[0]))>
{
    using R = decltype(f(c[0]));
    std::vector<R> v;
    std::transform(std::begin(c), std::end(c), std::back_inserter(v), f);
    return v;
}

选项 #2:

基本std::result_of&lt;T&gt;用法:

template <typename T, typename F, typename R = typename std::result_of<F&(T)>::type>
std::vector<R> select(const std::vector<T>& c, F f)
{
    std::vector<R> v;
    std::transform(std::begin(c), std::end(c), std::back_inserter(v), f);
    return v;
}

选项#3:

高级decltype() 用法和完美转发(见注释*):

template <typename T, typename A, typename F>
auto select(const std::vector<T, A>& c, F&& f)
    -> std::vector<typename std::decay<decltype(std::declval<typename std::decay<F>::type&>()(*c.begin()))>::type>
{
    using R = typename std::decay<decltype(std::declval<typename std::decay<F>::type&>()(*c.begin()))>::type;
    std::vector<R> v;
    std::transform(std::begin(c), std::end(c)
                 , std::back_inserter(v)
                 , std::forward<F>(f));
    return v;
}

选项#4:

高级std::result_of&lt;T&gt; 用法和完美转发(见注释*):

template <typename T, typename A, typename F, typename R = typename std::decay<typename std::result_of<typename std::decay<F>::type&(typename std::vector<T, A>::const_reference)>::type>::type>
std::vector<R> select(const std::vector<T, A>& c, F&& f)
{
    std::vector<R> v;
    std::transform(std::begin(c), std::end(c)
                 , std::back_inserter(v)
                 , std::forward<F>(f));
    return v;
}

* 注意: 选项#3 和#4 假设std::transform 算法采用按值 的函数对象,然后将其用作非常量左值。这就是为什么人们会看到这种奇怪的typename std::decay&lt;F&gt;::type&amp; 语法。如果函数对象应该在 select 函数本身内调用,并且结果类型不会用作容器的模板参数(出于 outer-most @ 987654332@),那么获取返回类型的正确且可移植的语法是:

/*#3*/ using R = decltype(std::forward<F>(f)(*c.begin()));

/*#4*/ typename R = typename std::result_of<F&&(typename std::vector<T, A>::const_reference)>::type

【讨论】:

  • IMO 的“小”改进让它看起来更好。
  • 我个人也更喜欢滥用“用于签名的类型定义”的模板参数,特别是当它们在正文中也需要时。 ;) 恕我直言,这比尾随返回类型更好(但请记住,它在技术上有所不同:它确实添加了模板参数,尽管您通常不需要关心它们)。
【解决方案2】:

您的第一个问题是您认为 lambda 是 std::functionstd::function 和 lambda 是不相关的类型。 std::function&lt;R(A...)&gt; 是一个类型擦除对象,它可以转换任何 (A) 可复制、(B) 可销毁和 (C) 可以使用 A... 调用并返回与 R 兼容的类型,并擦除有关的所有其他信息类型。

这意味着它可以使用完全不相关的类型,只要它们通过了这些测试。

lambda 是一个可销毁的匿名类,可以复制(C++14 除外,有时会复制),并具有您指定的operator()。这意味着您通常可以将 lambda 转换为具有兼容签名的 std::function

从 lambda 推导出 std::function 不是一个好主意(有办法做到这一点,但它们是坏主意:C++14 auto lambda 破坏了它们,而且你会得到不必要的低效率。)

那么我们如何解决您的问题?在我看来,您的问题是获取一个函数对象和一个容器,并推断在将函数对象应用于每个元素后会产生什么样的元素 transform,因此您可以将结果存储在 std::vector 中。

这是最接近您问题解决方案的答案:

template<typename T, typename R, typename Selector>
std::vector<R> select(std::vector<T> const & c, Selector s) {
  std::vector<R> v;
  std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
  return v;
}

最简单的方法是按模板顺序交换TR,并让调用者显式传入R,例如select&lt;double&gt;。这样就可以推断出TSelector。这并不理想,但确实有一点改进。

对于完整的解决方案,有两种方法可以解决此解决方案。首先,我们可以更改select 以返回一个带有operator std::vector&lt;R&gt; 的临时对象,从而将转换延迟到该点。这是一个不完整的草图:

template<typename T, typename Selector>
struct select_result {
  std::vector<T> const& c;
  Selector s;
  select_result(select_result&&)=default;
  select_result(std::vector<T> const & c_, Selector&& s_):
    c(c_), s(std::forward<Selector>(s_)
  {}
  operator std::vector<R>()&& {
    std::vector<R> v;
    std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
    return v;
  }
};
template<typename T, typename Selector>
select_result<T, Selector> select(std::vector<T> const & c, Selector&& s) {
  return {c, std::forward<Selector>(s)};
}

我还可以提供a slicker version,它可悲地依赖于未定义的行为(函数中本地引用的引用捕获在标准下存在生命周期问题)。

但这摆脱了auto v = select 语法——你最终会存储产生结果的东西,而不是结果。

您仍然可以使用std::vector&lt;double&gt; r = select( in_vec, [](int x){return x*1.5;} );,并且效果很好。

基本上我将推导分为两个阶段,一个用于参数,一个用于返回值。

但是,几乎不需要依赖该解决方案,因为还有其他更直接的方法。

对于第二种方法,我们可以自己推导出R

template<typename T, typename Selector>
std::vector<typename std::result_of<Selector(T)>::type>
select(std::vector<T> const & c, Selector s) {
  using R = typename std::result_of<Selector(T)>::type;
  std::vector<R> v;
  std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
  return v;
}

这是一个非常可靠的解决方案。稍微清理一下:

// std::transform takes by-value, then uses an lvalue:
template<class T>
using decayed_lvalue = typename std::decay<T>::type&; 
template<
  typename T, typename A,
  typename Selector,
  typename R=typename std::result_of<decayed_lvalue<Selector>(T)>::type
>
std::vector<R> select(std::vector<T, A> const & c, Selector&& s) {
  std::vector<R> v;
  std::transform(begin(c), end(c), back_inserter(v), std::forward<Selector>(s));
  return v;
}

使它成为一个可用的解决方案。 (将R 移动到template 类型列表,允许vector 的替代分配器,删除一些不必要的std::,并在Selector 上进行了完美转发。

但是,我们可以做得更好。

输入是vector这一事实毫无意义:

template<
  typename Range,
  typename Selector,
  typename R=typename std::result_of<Selector(T)>::type
>
std::vector<R> select(Range&& in, Selector&& s) {
  std::vector<R> v;
  using std::begin; using std::end;
  std::transform(begin(in), end(in), back_inserter(v), std::forward<Selector>(s));
  return v;
}

由于无法确定T 而无法编译。所以让我们继续努力吧:

namespace details {
  namespace adl_aux {
    // a namespace where we can do argument dependent lookup on begin and end
    using std::begin; using std::end;
    // no implementation, just used to help with ADL based decltypes:
    template<class R>
    decltype( begin( std::declval<R>() ) ) adl_begin(R&&);
    template<class R>
    decltype( end( std::declval<R>() ) ) adl_end(R&&);
  }
  // pull them into the details namespace:
  using adl_aux::adl_begin;
  using adl_aux::adl_end;
}
// two aliases.  The first takes a Range or Container, and gives
// you the iterator type:
template<class Range>
using iterator = decltype( details::adl_begin( std::declval<Range&>() ) );
// the second is syntactic sugar on top of `std::iterator_traits`:
template<class Iterator>
using value_type = typename std::iterator_traits<Iterator>::value_type;

这给了我们iterator&lt;Range&gt;value_type&lt;Iterator&gt; 别名。他们一起让我们轻松推断T

// std::transform takes by-value, then uses an lvalue:
template<class T>
using decayed_lvalue = typename std::decay<T>::type&; 

template<
  typename Range,
  typename Selector,
  typename T=value_type<iterator<Range&>>,
  typename R=typename std::result_of<decayed_lvalue<Selector>(T)>::type
>
std::vector<R> select(Range&& in, Selector&& s) {
  std::vector<R> v;
  using std::begin; using std::end;
  std::transform(begin(in), end(in), back_inserter(v), std::forward<Selector>(s));
  return v;
}

bob is your uncle。 (decayed_lvalue 反映了 Selector 类型如何用于极端情况,iterator&lt;Range&amp;&gt; 反映我们从 Range 的左值版本获取迭代器。

在 VS2013 中,有时上面的 decltypes 会混淆他们拥有的 C++11 的半实现。用 decltype(details::adl_begin(std::declval&lt;Range&gt;())) 替换 iterator&lt;Range&gt; 就可以解决这个问题。

// std::transform takes by-value, then uses an lvalue:
template<class T>
using decayed_lvalue = typename std::decay<T>::type&; 

template<
  typename Range,
  typename Selector,
  typename T=value_type<decltype(details::adl_begin(std::declval<Range&>()))>,
  typename R=typename std::result_of<decayed_lvalue<Selector>(T)>::type
>
std::vector<R> select(Range&& in, Selector&& s) {
  std::vector<R> v;
  using std::begin; using std::end;
  std::transform(begin(in), end(in), back_inserter(v), std::forward<Selector>(s));
  return v;
}

结果函数将采用数组、向量、列表、映射或自定义编写的容器,并将采用任何转换函数,并生成结果类型的向量。

下一步是让转换变得惰性,而不是直接放入vector。如果您需要摆脱惰性求值,您可以使用 as_vector 获取范围并将其写入向量。但这是在编写整个库而不是解决您的问题。

【讨论】:

  • 我知道 lambda 不是 std::function,但你完全正确,我弄乱了参数,应该是 R(T) 而不是 T(R)。感谢您提供信息丰富的答案。
  • @MariusBancila 编辑了您的帖子,因为T(R)R(T) 不是您问题的核心。添加了“返回魔法对象”技术的草图。还改进了其他解决方案,使其不那么笨拙,并包括一个我认为可以在 VS2013U1-U3 中使用的显式版本。我在上面留下了关于 std::function 的评论,因为这是您尝试失败的关键所在,并且可能对其他人有用。
  • 关于您的operator std::vector&lt;R&gt;()&amp;&amp;:返回本地值的右值引用(这是发生在这里,对吗?)不是一个坏主意吗?我很高兴了解更多关于返回右值引用的信息……有什么好的阅读建议吗?谢谢。
  • @Yakk 我不太明白你想说什么...operator std::vector&lt;R&gt;()&amp;&amp; 是什么意思?刚才才看到&amp;&amp;()后面;这是否意味着this&amp;&amp; 而要转换为的类型是一个值?
  • @PiotrS。修复了我认为的问题,还有iterator&lt;Range&gt;。将衰减左值捆绑到一个助手中,因为在那条线上放那么多体操会分散注意力。
猜你喜欢
  • 1970-01-01
  • 2020-06-22
  • 2019-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多