【问题标题】:Using a type that depends on lambda function as a return type使用依赖于 lambda 函数的类型作为返回类型
【发布时间】:2015-06-08 15:07:29
【问题描述】:

我想创建一个以 lambda 作为参数的函数,并返回一个类型取决于 lambda 函数返回类型的对象。 我想要实现的基本上是在实例化时没有显式的模板参数。

现在,这是我的解决方案,我的问题是:有没有更短(更优雅)的方法?

template<typename Func, typename RT = std::unordered_map<int,
  decltype(((Func*)nullptr)->operator()(T())) > >
RT mapResult(Func func)
{
  RT r;
  for (auto &i : mData)
    r.insert({i.first, func(mData.second)});
  return r;
}

为了更清楚一点,lambda 类型FuncT&amp; 为参数并返回一个特定类型的向量,mapResultfunc 的结果映射到一个unordered_map 的@ 987654327@ 模板参数是 lambda 函数返回类型(可能是别的东西,但仍然依赖于这种类型)。实际的代码要复杂得多,但我正在努力明确这一点。

我发现避免多次编写RT 类型的唯一解决方案是将它放在模板参数列表中并给它一个默认值,这取决于第一个模板参数(它本身是从函数参数推导出来的) .这有点像定义模板类型名。

我正在使用 VC12,但也想拥有在 g++ 下编译的可移植代码。

然后实例化看起来像这样(虚拟示例):

auto r = c.mapResult([](T &t){return std::vector<int> {(int)t.size()};});

【问题讨论】:

  • " 如果我​​从 decltype 中删除一组括号,它就不起作用" 你在说哪组括号?
  • @dyp 可能是我的阅读错误。从此代码中,您无法删除括号。我不确定我的原始代码是否有一组额外的括号。编辑:删除问题。
  • 演员 ((Func*)nullptr) 需要一组 () 因为您使用的是(C 风格)演员表。语法简单地将(T)e-&gt;x 解释为(T)(e-&gt;x)。您可以改用函数样式转换 T(e)-&gt;x,但这不适用于 Func*,因为它不是简单类型说明符。其他演员,如static_cast&lt;T&gt;(e)-&gt;x 会起作用。最后的括号是operator() 的函数调用括号,加上T 类型的“强制转换”(创建临时)的括号。它可以写成operator()( T{} ),或者只是(*static_cast&lt;Func*&gt;(nullptr))( T{} )

标签: c++ templates c++11 lambda decltype


【解决方案1】:

C++11 标准库包含一个名为 result_of 的元函数。此元函数计算函数对象的返回类型。可能是由于它在 boost(和 C++03)中的历史,它以一种相当特殊的方式使用:您将函数对象的类型和要调用函数对象的参数的类型通过组合传递给它函数类型。例如:

struct my_function_object
{
    bool operator()(int);
    char operator()(double);
};

std::result_of<my_function_object(int)>::type // yields bool
std::result_of<my_function_object(short)>::type // yields bool
std::result_of<my_function_object(double)>::type // yields char

result_of 执行重载决议。如果您调用short s{}; my_function_object{}(s);,重载决议将选择my_function_object::operator()(int)。因此,对应的result_of&lt;my_function_object(short)&gt;::type 产生bool

使用这个特性,你可以简化返回类型的计算,如下所示:

template<typename Func, typename RT = std::unordered_map<int,
  typename std::result_of<Func(T&)>::type > >
RT mapResult(Func func)
{
  RT r;
  for (auto &i : mData)
    r.insert({i.first, func(i.second)});
  return r;
}

T&amp; 参数告诉result_of 在重载决议中使用左值参数。默认值(对于非引用类型 T)是 xvalue (T&amp;&amp;)。

OP 的版本有一点不同:使用std::result_of(在 C++11 中),SFINAE 可能无法正常工作。这在 C++14 中得到了解决。见N3462


C++14 引入了标准化的别名模板,例如 result_of_t,因此您可以摆脱 typename::type

template<typename Func, typename RT = std::unordered_map<int,
  std::result_of_t<Func(T&)> > >
RT mapResult(Func func)
{
  RT r;
  for (auto &i : mData)
    r.insert({i.first, func(i.second)});
  return r;
}

如果您使用的是 Visual Studio 2013 或更新版本,您可以自己编写别名模板。您还可以更进一步,将整个返回类型编写为元函数:

template<typename FT> using result_of_t = typename std::result_of<FT>::type;
template<typename Func> using RetType =
    std::unordered_map<int, result_of_t<Func(T&)> >;

template<typename Func, typename RT = RetType<Func> >
RT mapResult(Func func)
{
  RT r;
  for (auto &i : mData)
    r.insert({i.first, func(i.second)});
  return r;
}

当然,如果你有足够的 C++14 核心语言支持(VS12 中没有),你也可以使用返回类型推导:

template<typename Func>
auto mapResult(Func func)
{
  auto r = std::unordered_map<int, result_of_t<Func(T&)>>{};
  for (auto &i : mData)
    r.insert({i.first, func(i.second)});
  return r;
}

也可以使用decltype来缩短版本:

using std::declval;
decltype(declval<Func>(T{}))

虽然这不太正确,但函数对象和参数都是左值:

decltype(declval<Func&>(declval<T&>{}))

declval 将在重载决议中为非引用类型X 使用 xvalue。通过添加&amp;,我们告诉它使用左值。 (result_of 基于declval,所以两者都表现出这种行为。)


请注意,在任何情况下,通过 std::decay 元函数运行 result_of_t&lt;Func(T&amp;)&gt; 类型可能会很有用,例如:在以下情况下出现的参考文献:

[](string const& s) -> string const& { return s; } // identity

不过,这确实取决于您的用例,并且任何一种选择都应记录在案。


IIRC,emplace 在这种情况下(在理论上)稍微更有效(插入唯一元素):

r.emplace(i.first, func(i.second));

也许可以进一步优化这个功能,例如通过在插入之前保留桶数,或者使用迭代器适配器来利用构造函数进行插入。使用std::transform 也应该是可能的,尽管我猜想由于value_type 对的额外移动,它不会那么有效。

【讨论】:

  • 感谢您提供所有详细信息!关于 emplace,谢谢,我不知道(我还没有读过 C++ 书,所以我会尽我所能得到的帮助,直到我这样做:))。关于 std::decay :所有 lambda 返回要么是即时返回(右值),要么是在 lambda 中创建的对象(如果我没记错的话,应该总是按值返回)。此外,模板别名可能允许我从模板参数列表中删除返回类型(因为我只需要编写两次一小部分代码),如 Veritas 的回答所示。
  • @Veritas SO 用户似乎认为你的问题和我的问题一样有价值,我们的票数相同。
  • @Veritas 很抱歉误导了您!正如我在上一条评论中所写的那样,您的回答是对这个回答的一个很好的补充(我猜它从未出现过)
【解决方案2】:

在 C++11 中,您可以做一些事情来使其成为分类器。一种方法是使用模板别名:

namespace details
{
template <typename Func>
using map_type_t = 
    std::unordered_map<int, typename std::result_of<Func(T)>::type>>;
}

template <typename Func>
details::map_type_t<Func> mapResult(Func func)
{
    details::map_type_t<Func> r;
    //...
    return r;
}

在 C++14 中,您可以将返回类型推导留给编译器:

template <typename Func>
auto mapResult(Func func)
{
    std::unordered_map<int, decltype(func(T()))> r;
    //...
    return r;
}

【讨论】:

  • 我同意第二个解决方案(但我不能使用它),而不是第一个解决方案,因为它迫使您将类型写入两次。实际的类型要长得多,我想把它放在一个地方。
  • 使用别名实际上可以更好地删除额外的模板参数(如您所建议的)。我在上面的评论中提到过。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-19
  • 1970-01-01
  • 2021-04-02
  • 2023-03-05
相关资源
最近更新 更多