具有相同签名和捕获的常见lambda类型

时间:2015-02-01 13:29:01

标签: c++ c++11 types lambda c++14

假设我有一些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之间的东西。是否存在类似的东西?并且......可以实际实现这样的东西吗?

4 个答案:

答案 0 :(得分:5)

C ++标准部分§5.1.2[expr.prim.lambda]:

  

lambda表达式的类型(也是闭包对象的类型)是唯一,未命名的非联合类类型 - 称为闭包类型

每个lambda都有不同的类型:l0l1l2没有共同类型。

因此,请考虑变体类型的std::vector<>,例如boost.variant(如果您知道lambda类型的集合),或使用std::function<>,这似乎也适合。


带有boost::variant

示例

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::functionboost::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()。我可以告诉你它有效,但对语言可能不礼貌。