假设我有一些lambdas与完全相同的捕获和完全相同的签名。
int captured;
auto l0 = [&captured](int x){ captured += x; };
auto l1 = [&captured](int x){ captured -= x; };
auto l2 = [&captured](int x){ captured = x + 1; };
现在,假设我需要将这些lambda存储在std::vector
中,以便在运行时调用它们。
我不能使用原始函数指针,因为捕获的变量强制lambda成为仿函数,而不是传统的函数。
我可以使用std::function
,但这太过分了,因为我确信所有的lambda都具有相同的签名和相同的捕获。由于std::function
支持具有相同签名但不同捕获的lambda,我(很可能)支付额外的运行时成本,可能是(?) 避免。
std::vector<decltype(l0)> v0; // Ok
v0.emplace_back(l0); // Ok
v1.emplace_back(l1); // Nope: `decltype(l0) != decltype(l1)`
v2.emplace_back(l2); // Nope: `decltype(l0) != decltype(l2)`
我想找到所有lambda之间的常见类型,但std::common_type
不起作用。
// Nope: does not compile
using LCT = std::common_type_t<decltype(l0), decltype(l1), decltype(l2)>;
基本上,我需要原始函数指针和std::function
之间的东西。是否存在类似的东西?并且......可以实际实现这样的东西吗?
答案 0 :(得分:5)
C ++标准部分§5.1.2[expr.prim.lambda]:
lambda表达式的类型(也是闭包对象的类型)是唯一,未命名的非联合类类型 - 称为闭包类型
每个lambda都有不同的类型:l0
,l1
和l2
没有共同类型。
因此,请考虑变体类型的std::vector<>
,例如boost.variant(如果您知道lambda类型的集合),或使用std::function<>
,这似乎也适合。
示例:
int main () {
int captured = 42;
auto l0 = [&captured](int x){ captured += x; };
auto l1 = [&captured](int x){ captured -= x; };
auto l2 = [&captured](int x){ captured = x + 1; };
std::vector<boost::variant< decltype(l0), decltype(l1), decltype(l2)>> variant;
variant.push_back(l0);
variant.push_back(l1);
variant.push_back(l2);
auto f = boost::get<decltype(l1)>(variant[1]);
int i = 1;
f(i);
std::cout << captured;
}
<强> Demo 强>
注意:强>
正如Johannes Schaub所指出的,像这样的lambda变体不是默认的可构造的,即你不能写:
boost::variant< decltype(l0), decltype(l1), decltype(l2)> v;
std::function<>
是默认的可构造的......
答案 1 :(得分:2)
记住lambda是什么:函数对象的简写,可以在C ++ 98中手写。
你的三个lambdas相当于以下内容:
int captured;
struct l0_t {
int& captured;
l0_t(int& _captured) : captured(_captured) {}
void operator()(int x) const { captured += x; }
} l0(captured);
struct l1_t {
int& captured;
l1_t(int& _captured) : captured(_captured) {}
void operator()(int x) const { captured -= x; }
} l1(captured);
struct l2_t {
int& captured;
l2_t(int& _captured) : captured(_captured) {}
void operator()(int x) const { captured = x + 1; }
} l2(captured);
鉴于此,如果您希望能够以多态方式处理这三个对象,那么您需要某种虚拟调度,这正是std::function
或boost::variant
将为您提供的。
如果你愿意放弃lambdas,一个更简单的解决方案将是一个具有三个不同成员函数的单个类,以及一个vector
指向该类成员函数的指针,因为它们已经存在。没有理由让向量的每个元素都有自己对被捕获对象的引用:
struct f {
int& captured;
f(int& _captured) : captured(_captured) {}
void f0(int x) const { captured += x; }
void f1(int x) const { captured -= x; }
void f2(int x) const { captured = x + 1; }
};
int captured = 0;
f multiplex(captured);
std::vector<decltype(&f::f0)> fv { &f::f0, &f::f1, &f::f2 };
for (auto&& fn : fv) {
(multiplex.*fn)(42);
std::cout << captured << "\n";
}
答案 2 :(得分:2)
根据你对我的评论的回答,我认为这是(非常粗略地)你想要的:
#include <iostream>
#include <type_traits>
#include <vector>
template<typename L, typename R, typename... Args> struct lambda_hack
{
using storage_type = std::aligned_storage_t<sizeof(L), std::alignment_of<L>::value>;
static storage_type storage;
static void init_data(const L& arg) { new(&storage) L(arg); }
template<typename LL> static R call_target(Args... args) { return reinterpret_cast<LL&>(storage)(args...); }
template<typename LL> lambda_hack(LL&&) : target(call_target<LL>) { }
using target_type = R(*)(Args...);
target_type target;
R operator()(Args... args) const { return target(args...); }
};
template<typename L, typename R, typename... Args>
typename lambda_hack<L, R, Args...>::storage_type lambda_hack<L, R, Args...>::storage;
int main()
{
int captured = 7;
auto l0 = [&captured](int x){ captured += x; };
auto l1 = [&captured](int x){ captured -= x; };
auto l2 = [&captured](int x){ captured = x + 1; };
using lhack = lambda_hack<decltype(l0), void, int>;
lhack::init_data(l0);
std::vector<lhack> v{l0, l1, l2};
for(auto& h : v)
{
std::cout << "'captured' before: " << captured << '\n';
h(3);
std::cout << "'captured' after: " << captured << '\n' << '\n';
}
std::cout << captured << '\n'; // prints '4', as expected
}
存储在std::vector
中的仿函数只是一个非成员函数指针的大小。实际捕获的数据仅单独存储一次。在这样的仿函数上调用operator()
只需要一个间接通过该指针的开销(比虚函数调用更好)。
它以C ++ 14模式和VC ++ 2013编译并运行在GCC 4.9.1和Clang 3.5.0上。
将此视为您在生产中实际使用的 alpha版本。它需要精炼(例如,它不会正确地破坏静态存储)。我想首先看看这是否确实是你想要的。
要解决的第一件事可能是storage
不应该static
。由于一组这样的lambda本质上非常密切相关,你可能想要将它们存储在容器中,正如你在问题中提到的那样。由于只要该容器存在,storage
需要可用,我将它存储在容器本身(子类std::vector
,也许?...)并在容器中销毁它的内容被毁了。
答案 3 :(得分:0)
它不存在。
您可以使用std::function
。您可以使用boost::variant
。或者你可以编写自己的类型擦除类型。
存储one_of_these_function
或重新实现它的boost::variant
,并公开使用变体上的访问者调用正确方法的特定operator()
签名将合理有效地解决您的问题。
另一个有点疯狂的选择就是编写自己的函数,比如基于类的#34;最快的代表&#34;技术,假设上面的lambda是一个指针的大小,可以被视为平凡的可复制,并使用tomfoolery伪存储它们并在指向所述存储指针的指针上调用operator()
。我可以告诉你它有效,但对语言可能不礼貌。