使用依赖于lambda函数的类型作为返回类型

时间:2015-06-08 15:07:30

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

我想创建一个以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&作为参数并返回特定类型的向量,mapResult映射func的结果在unordered_map _Ty模板参数是lambda函数返回类型(可能是其他东西,但仍依赖于此类型)。实际的代码要复杂得多,但我正在努力明确这一点。

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

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

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

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

2 个答案:

答案 0 :(得分:8)

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<my_function_object(short)>::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&参数告诉result_of在重载决策中使用左值参数。默认值(对于非引用类型T)是xvalue(T&&)。

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将在重载解析中使用xvalue作为非引用类型X。通过添加&,我们告诉它使用左值。 (result_of基于declval,因此都显示此行为。)

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

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

这取决于您的用例,并且应该记录任何一个选择。

IIRC,emplace在这种情况下(理论上)更有效(插入独特元素):

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

有可能进一步优化此功能,例如通过在插入之前保留存储桶计数,或者使用迭代器适配器来利用构造函数进行插入。使用std::transform也应该是可能的,但我猜它由于value_type对的额外移动而无法提高效率。

答案 1 :(得分:4)

在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;
}