C++ 中 lambda 的前向声明

Forward declaration of lambdas in C++

在C++中,可以将函数的声明和定义分开。比如声明一个函数是很正常的:

int Foo(int x);

Foo.h中实现并在Foo.cpp中实现。是否可以用 lambda 做类似的事情?例如,定义 a

std::function<int(int)> bar;

bar.h中并在bar.cpp中实现为:

std::function<int(int)> bar = [](int n)
{
    if (n >= 5) 
        return n;
    return n*(n + 1);
};

免责声明:我有在 C# 中使用 lambda 的经验,但我在 C++ 中用得不多。

您不能将 lambdas, neither forward declare it. Its type is a unique unnamed closure type which is declared with the lambda expression. But you could do that with std::function objects 的声明和定义分开,它旨在能够存储任何可调用目标,包括 lambda。

正如您的示例代码所示,您一直在使用 std::function,请注意,对于这种情况,bar 确实是一个全局变量,您需要在 header 文件使其成为声明(而不是定义)。

// bar.h
extern std::function<int(int)> bar;     // declaration

// bar.cpp
std::function<int(int)> bar = [](int n) // definition
{
    if (n >= 5) return n;
    return n*(n + 1);
};

再次注意,这不是lambda的单独声明和定义;它只是一个全局变量 bar 的单独声明和定义,类型为 std::function<int(int)>,它是从 lambda 表达式初始化的。

严格来说你不能

引用自cpp reference

The lambda expression is a prvalue expression whose value is (until C++17)whose result object is (since C++17) an unnamed temporary object of unique unnamed non-union non-aggregate class type, known as closure type, which is declared (for the purposes of ADL) in the smallest block scope, class scope, or namespace scope that contains the lambda expression

所以 lambda 是一个未命名的临时对象。您可以将 lambda 绑定到左值对象(例如 std::function),并且根据有关变量声明的常规规则,您可以将声明和定义分开。

前向声明不是正确的术语,因为 C++ 中的 lambda 是对象,而不是函数。代码:

std::function<int(int)> bar;

声明了一个变量,您不必强制分配它(该类型的默认值为 "pointer to no function")。您甚至可以编译对它的调用...例如代码:

#include <functional>
#include <iostream>

int main(int argc, const char *argv[]) {
    std::function<int(int)> bar;
    std::cout << bar(21) << "\n";
    return 0;
}

会干净地编译(但当然会在运行时表现得很疯狂)。

也就是说,您可以将 lambda 分配给兼容的 std::function 变量并添加例如:

bar = [](int x){ return x*2; };

就在调用之前将生成一个可以正常编译并生成输出 42 的程序。

关于 C++ 中的 lambda 的一些不明显的事情可能令人惊讶(如果您知道其他具有此概念的语言)是

  • 每个 lambda [..](...){...} 都有不同的不兼容类型,即使签名完全相同。例如,您不能声明 lambda 类型的参数,因为唯一的方法是使用 decltype([] ...) 之类的东西,但是这样就无法像调用站点上的任何其他 []... 形式那样调用该函数不兼容。这是由 std::function 解决的,所以如果你必须传递 lambda 或将它们存储在容器中,你必须使用 std::function.

  • Lambda 可以通过值(但它们是 const 除非您声明 lambda mutable)或通过引用(但保证引用对象的生命周期将不能短于 lambda 的生命周期(由程序员决定)。 C++ 没有垃圾收集器,这是正确解决 "upward funarg" 问题所必需的(您可以通过捕获智能指针来解决,但必须注意引用循环以避免泄漏)。

  • 与其他语言不同的是,lambda 可以被复制,当你复制它们时,你正在拍摄它们内部捕获的按值变量的快照。这对于可变状态来说是非常令人惊讶的,这就是我认为默认情况下按值捕获的值是 const 的原因。

一种合理化和记住有关 lambda 的许多细节的方法是这样的代码:

std::function<int(int)> timesK(int k) {
    return [k](int x){ return x*k; };
}

基本上是

std::function<int(int)> timesK(int k) {
    struct __Lambda6502 {
        int k;
        __Lambda6502(int k) : k(k) {}
        int operator()(int x) {
            return x * k;
        }
    };
    return __Lambda6502(k);
}

有一点细微的差别,即甚至可以复制 lambda 捕获引用(通常 类 包含作为成员的引用不能)。