具有相同签名和捕获的常见 lambda 类型
Common type of lambdas with same signature and captures
假设我有一些具有完全相同捕获和完全相同签名的lambda。
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
之间的东西。有这样的事情存在吗?和。。。 这样的事情可以实际实施吗?
C++ 标准节 § 5.1.2 [expr.prim.lambda] :
lambda 表达式的类型(也是闭包对象的类型)是一种唯一的、未命名的非联合类类型,称为闭包类型
每个lambda都有不同的类型:l0
、l1
和l2
没有共同的类型。
因此,请考虑变体类型的std::vector<>
,例如 boost.varial(如果您知道 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;
}
演示
注意:
正如Johannes Schaub所指出的,像这样的lambda变体不是默认可构造的,即你不能写:
boost::variant< decltype(l0), decltype(l1), decltype(l2)> v;
而std::function<>
是默认可构造的。
根据您对我评论的回答,我认为这是(非常粗略地)您想要的:
#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 模式在 GCC 4.9.1 和 Clang 3.5.0 以及 VC++ 2013 上编译和运行。
将此视为您在生产中实际使用的 alpha 版本。它需要优化(例如,它不会正确破坏静态存储)。我想先看看这是否真的是你要找的。
首先要解决的可能是这样一个事实,即storage
不应该被static
。由于一组这样的 lambda 本质上是非常密切相关的,因此您可能希望将它们存储在容器中,正如您在问题中提到的。由于storage
需要在该容器存在期间可用,因此我会将其存储在容器本身(子类std::vector
,也许?...)中,并在容器被销毁时销毁其内容。
记住lambda是什么:函数对象的简写,可以用C++98手动编写。
您的三个 lambda 等效于以下内容:
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";
}
它不存在。
您可以使用std::function
. 您可以使用boost::variant
. 或者,您可以编写自己的类型擦除类型。
存储boost::variant
或重新实现它并公开特定operator()
签名(使用变体上的访问者调用正确方法)的one_of_these_function
将合理有效地解决您的问题。
另一个有点疯狂的选择是基于"最快的可能委托"技术编写自己的函数,如类,假设上面的 lambda 是一个指针的大小,可以被视为微不足道的可复制,并使用愚蠢来伪存储它们并在指向所述存储指针的指针上调用operator()
。 我可以告诉你它有效,但它可能对语言不礼貌。
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 可组合的lambda/std::函数与std::可选
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- 如何建立使用模板函数的lambda函数的尾部返回类型
- 如何将lambda作为模板类的成员函数参数
- C++从其他 constexpr 创建 lambda 不能按顺序执行 Constexpr
- 在 lambda 捕获中声明的变量的类型推导
- 我可以将调用类的"this"传递给 lambda 函数吗?
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 为什么lambda在clang上崩溃而不是在gcc上崩溃
- 模板函数指针和lambda
- 两组使用lambda函数的大括号
- 使lambda不可复制/不可移动
- FLTK:按下哪个按钮 - 将数字传递给按钮的回调 (lambda)
- 尝试将lambda函数放在队列中时出现一般分配器错误(可能是与unique_ptr有关的错误)
- 将带有unique_ptr的可变 lambda 传递给 const&std::function
- AWS Lambda C++运行时权限被拒绝
- 捕获lambda中的std::数组
- 具有相同签名和捕获的常见 lambda 类型
- 用于 lambda 和函数的常见类型转换,稍后要引用