这可以做到,但它 a) 有点复杂,b) 在 99.9% 的情况下,这并不是一个好主意。这是你如何进行的。您可以根据分配表达式的类型执行某些操作的唯一方法是利用隐式转换。这需要一个模板化的隐式对话运算符,它不能在 lambda 中本地声明,所以我们必须从编写一些支持代码开始:
template <class T>
struct identity {
T get(); // not defined
};
template <class F>
struct ReturnConverter {
F f;
template <class T>
operator T() {
return f(identity<T>{});
}
};
template <class F>
auto makeReturnConverter(F f) { return ReturnConverter<F>{f}; }
第一个类只是帮助 lambda 推断类型。第二个类是一个本身接受 lambda(或任何可调用)的类,并且具有到任何类型的隐式转换运算符。当请求转换时,它调用可调用对象,使用我们的identity 类模板作为输入类型的一种方式。我们现在可以像这样使用它:
auto doIt = [] (long l) {
return makeReturnConverter([=] (auto t) {
return l + sizeof(decltype(t.get()));
});
};
这个 lambda 通过输入另一个 lambda 来创建我们特殊的 ReturnConverter 类。这个 lambda 捕获外部 lambda 的 long l 参数(按值),它准备接受我们的特殊身份类作为唯一参数。然后它可以退出“目标”或目标类型。我在这里使用sizeof 来快速展示一个示例,其中结果取决于 lambda 的参数和目标类型。但请注意,一旦我使用decltype(t.get()) 获得类型,我实际上可以声明该类型的变量并用它做任何我想做的事情。
float x = doIt(5);
double y = doIt(2);
在这些调用之后,x 将为 9(浮点数为 4,+5),y 为 10(双精度为 8,+2)。
这里有一个更有趣的例子:
auto doIt2 = [] (long l) {
return makeReturnConverter([=] (auto t) {
decltype(t.get()) x;
for (std::size_t i = 0; i != l; ++i ) {
x.push_back(i*i);
}
return x;
});
};
std::vector<int> x = doIt2(5);
std::deque<int> y = doIt2(5);
这里,只要标准容器有push_back方法,我就可以按照左边的要求一般地构建一个标准容器。
就像我在开始时所说的那样,在绝大多数情况下,这过于复杂且难以证明其合理性。通常您可以将特定类型作为显式(非推断)模板参数和auto 左侧。不过,我已经在非常具体的情况下使用了它。例如,在 gtest 中,当您声明测试夹具时,任何重用的数据都被声明为夹具中的成员变量。非静态成员变量必须用它们的类型声明(不能使用 auto),所以我使用这个技巧来允许快速构建某些类型的夹具数据,同时保持重复尽可能接近零。在这个用例中没问题,因为该代码不需要非常健壮,但确实希望不惜任何代价最小化重复;通常这不是一个很好的权衡(并且通常不需要左侧可用的auto)。