C++11 lambda作为数据成员?

76
可以将lambda定义为数据成员吗?
例如,是否可以使用lambda来重写下面的代码示例,而不是使用函数对象?
struct Foo {
    std::function<void()> bar;
};

我想知道的原因是因为以下的lambda函数可以作为参数传递:
template<typename Lambda>
void call_lambda(Lambda lambda) // what is the exact type here?
{ 
    lambda();
}

int test_foo() {
    call_lambda([] { std::cout << "lambda calling" << std::endl; });
}

我已经想到了,如果一个lambda可以作为函数参数传递,或许它们也可以被存储为数据成员。
在进一步尝试之后,我发现这是可行的(但有点没有意义)。
auto say_hello = [] { std::cout << "Hello"; };

struct Foo {
    using Bar = decltype(say_hello);
    Bar bar;
    Foo() : bar(say_hello) {}
};

2
Lambda表达式的类型名称对您来说是未知的,并由编译器生成(每个lambda函数一个)。std::function模板正是为这种情况引入的类型擦除器。 - Alexandre C.
1
你自己试过了吗?为什么要让我们替你做呢?如果出现错误,就来这里把错误信息发帖上来。 - Daniel
1
Lambda函数是函数对象!是的,你可以将“bar”定义为一个lambda。 - Kerrek SB
1
有些lambda表达式也可以成为函数指针。 - R. Martinho Fernandes
2
@Dani:嗯,C++标准委员会已经完成了一切工作,并制定了FDIS(最终草案国际标准)在三月份。现在只等待ISO的批准即可。 - Xeo
显示剩余5条评论
6个回答

59

一个lambda函数只是创建了一个函数对象,所以,是的,你可以用lambda初始化一个函数成员。以下是一个例子:

#include <functional>
#include <cmath>

struct Example {

  Example() {
    lambda = [](double x) { return int(std::round(x)); };
  };

  std::function<int(double)> lambda;

};

6
为什么不使用初始化列表? - Kerrek SB
现代编译器真的不优化这种情况吗? - Nevir
8
我在示例中故意没有把函数放在初始化列表中,因为我想,如果你在实际情况下真的像上面那样做的话,你的lambda函数可能会进行更复杂的捕获操作或其他一些操作,这时在初始化列表里将不太适合。但当然,如果你愿意,你可以这样写:Example() : lambda([](double x) { return int(std::round(x)); }) {}。 =) - wjl
1
由于Lambda表达式产生可调用对象,因此闭包可以存储在std :: function对象中。Lambda表达式并不仅仅是创建函数对象。 - sungiant
1
@sungiant 嗯,如果你想争论定义,那么 lambda 函数确实只是制造函数对象--但你正确的是,它们并不仅仅创建 std::function 对象。每个 lambda--即使是以完全相同的方式定义--都是一个独特类的函数对象。幸运的是,这些可以透明和多态地使用类型擦除包装器,例如 std::function。=) - wjl
2
Lambda表达式不能创建std::function<...>对象。尽管如此,它们可以隐式转换为一个。有几个重要的区别。首先,Lambda表达式不会进行堆分配,但是std::function会进行堆分配。没有任何捕获的Lambda实际上是普通函数类型(例如void (&)(int, char))。 - Thomas Eding

32

模板使无需类型抹除成为可能,但仅限于此:

template<typename T>
struct foo {
    T t;
};

template<typename T>
foo<typename std::decay<T>::type>
make_foo(T&& t)
{
    return { std::forward<T>(t) };
}

// ...
auto f = make_foo([] { return 42; });

重复之前已经提到的论点: []{} 不是一种类型,因此您无法像您尝试的那样将其用作模板参数。使用 decltype 也很棘手,因为每个 lambda 表达式实例都是一个独立的闭包对象符号,具有唯一的类型。(例如,上面的 f 的类型不是 foo<decltype([] { return 42; })>。)

除非进行类型擦除,否则无法在不知道其类型的情况下拥有函数类型的成员变量。 - Alexandre C.
@Alexandre 这不是关于 std::function 的问题,假设你是这个意思。如果不是的话,我就不明白了。 - Luc Danton
不一定。我的意思是说,您不能在不知道T的情况下声明类型为foo <T> 的成员变量。要做到这一点,您必须以某种方式擦除T。这限制了您使用该方法的可能性(顺便说一句,这很好,我学习了std :: decay并提醒自己尽可能使用初始化列表)。 - Alexandre C.
1
@Alexandre 这就是顶部所说的“无类型擦除”的含义。 - Luc Danton
2
在我的情况下,数组中的所有函数具有相同的参数和返回类型非常重要,因此普通的std::function就可以很好地工作。我仍然在考虑开销问题。 - Miles

21

有点晚了,但我在这里没有看到这个答案。如果lambda没有捕获参数,那么它可以隐式地转换为一个具有相同参数和返回类型的函数指针。

例如,以下程序编译良好并执行了您所期望的操作:

struct a {
    int (*func)(int, int);
};

int main()
{
    a var;
    var.func = [](int a, int b) { return a+b; };
}

当然,lambda函数的主要优势之一是捕获子句,一旦添加了捕获子句,那么这个技巧就不再起作用。像上面回答的那样使用std::function或模板。


10
#include <functional>

struct Foo {
    std::function<void()> bar;
};

void hello(const std::string & name) {
    std::cout << "Hello " << name << "!" << std::endl;
}

int test_foo() {
    Foo f;
    f.bar = std::bind(hello, "John");

    // Alternatively: 
    f.bar = []() { hello("John"); };
    f.bar();
}

5
只要 lambda 是常量(没有闭包),你可以这样做:
#include <iostream>

template<auto function>
struct Foo
{
    decltype(function) bar = function;
};

void call_lambda(auto&& lambda)
{
    lambda();
}

int main()
{
    Foo<[](){ std::cout << "Hello"; }> foo;
    foo.bar();
    call_lambda(foo.bar);
}

https://godbolt.org/z/W5K1rexv3

或者我们可以应用推断指南,使其适用于所有Lambda表达式:

#include <iostream>

template<typename T>
struct Foo 
{
    T bar;
};

template<typename T>
Foo(T) -> Foo<std::decay_t<T>>;

void call_lambda(auto&& lambda)
{
    lambda();
}

int main()
{
    std::string hello = "Hello";
    Foo foo([&](){ std::cout << hello; });
    foo.bar();
    call_lambda(foo.bar);
}

https://godbolt.org/z/cenrzTbz4


3

如果一个lambda表达式可以作为函数参数传递,那么可能也可以作为成员变量。

第一个问题的答案是肯定的,您可以使用模板参数推断或“auto”来实现。第二个问题可能是否定的,因为您需要在声明点知道类型,而前两个技巧都不能用于此。

有一个可能有效的方法,但我不知道是否有效,那就是使用decltype。


decltype 不起作用是因为 lambda 表达式的每个 实例 都是不同类型的,它们之间也无法相互转换。但显然在 decltype 中甚至不能使用 lambda 表达式。 - R. Martinho Fernandes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接