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

max*_*xbc 3 c++ lambda templates decltype c++11

我想创建一个以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;
}
Run Code Online (Sandbox Code Playgroud)

为了使它更加清楚一点,λ类型Func需要T&的参数,并返回一个特定类型的载体,和mapResult结果映射的funcunordered_map_Ty模板参数是lambda函数的返回类型(可能是别的东西,但仍然依赖于这种类型).实际代码要复杂得多,但我正试图明确这一点.

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

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

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

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

dyp*_*dyp 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
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

OP的版本略有不同:SFINAE可能无法正常使用std::result_of(在C++ 11中).这在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;
}
Run Code Online (Sandbox Code Playgroud)

如果您使用的是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;
}
Run Code Online (Sandbox Code Playgroud)

当然,如果你有足够的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;
}
Run Code Online (Sandbox Code Playgroud)

还可以使用decltype以下方法缩短版本:

using std::declval;
decltype(declval<Func>(T{}))
Run Code Online (Sandbox Code Playgroud)

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

decltype(declval<Func&>(declval<T&>{}))
Run Code Online (Sandbox Code Playgroud)

declval将在重载分辨率中使用xvalue作为非引用类型X.通过添加&,我们告诉它使用左值.(result_of基于declval,所以两者都显示这种行为.)


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

[](string const& s) -> string const& { return s; } // identity
Run Code Online (Sandbox Code Playgroud)

这取决于您的用例,并且应记录任一选择.


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

r.emplace(i.first, func(i.second));
Run Code Online (Sandbox Code Playgroud)

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

  • @Veritas SO用户似乎认为你的问题和我一样有价值,我们的投票数相同. (2认同)