Forward declaration of lambdas in C++

Forward declaration is not the correct term because lambdas in C++ are objects, not functions. The code:

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

declares a variable and you're not forced to assign it (that type has a default value of "pointer to no function"). You can compile even calls to it... for example the code:

#include <functional>
#include <iostream>

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

will compile cleanly (but of course will behave crazily at runtime).

That said you can assign a lambda to a compatible std::function variable and adding for example:

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

right before the call will result in a program that compiles fine and generates as output 42.

A few non-obvious things that can be surprising about lambdas in C++ (if you know other languages that have this concept) are that

  • Each lambda [..](...){...} has a different incompatible type, even if the signature is absolutely identical. You for example cannot declare a parameter of lambda type because the only way would be to use something like decltype([] ...) but then there would be no way to call the function as any other []... form at a call site would be incompatible. This is solved by std::function so if you have to pass lambdas around or store them in containers you must use std::function.

  • Lambdas can capture locals by value (but they're const unless you declare the lambda mutable) or by reference (but guaranteeing the lifetime of the referenced object will not be shorter than the lifetime of the lambda is up to the programmer). C++ has no garbage collector and this is something needed to solve correctly the "upward funarg" problem (you can work-around by capturing smart pointers, but you must pay attention to reference loops to avoid leaks).

  • Differently from other languages lambdas can be copied and when you copy them you're taking a snapshot of their internal captured by-value variables. This can be very surprising for mutable state and this is I think the reason for which captured by-value values are const by default.

A way to rationalize and remember many of the details about lambdas is that code like:

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

is basically like

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);
}

with one subtle difference that even lambda capturing references can be copied (normally classes containing references as members cannot).


You can't separate declaration and definition of 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, which is designed to be able to store any callable target, including lambdas.

As your sample code shown you've been using std::function, just note that for this case bar is a global variable indeed, and you need to use extern in header file to make it a declaration (not a definition).

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

and

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

Note again that this is not separate declaration and definition of lambda; It's just separate declaration and definition of a global variable bar with type std::function<int(int)>, which is initialized from a lambda expression.


Strictly speaking you can't

Quoting from 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

So the lambda is a unnamed temporary object. You can bind the lambda to a l-value object(for eg std::function) and by regular rules about variable declaration you can separate the declaration and definition.