36

In C++, it is possible to separate the declaration and definition of functions. For example, it is quite normal to declare a function:

int Foo(int x);

in Foo.h and implement it in Foo.cpp. Is it possible to do something similar with lambdas? For example, define a

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

in bar.h and implement it in bar.cpp as:

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

Disclaimer: I have experience with lambdas in C#, but I have not used them in C++ very much.

MxNx
  • 1,342
  • 17
  • 28
  • 34
    If you're going to give the lambda a name, you may as well just make it a normal function. – Brian Bi Nov 22 '16 at 06:30
  • @Brian I know and it makes sense. The main reason that I ask the question is curiosity and I like to know the possibility of it. – MxNx Nov 22 '16 at 06:33
  • 8
    FYI; `std::function` is able to store lambdas; but lambdas do not have the type `std::function<...>`; but rather have their own unique unnamed type. You can store a lambda directly in a variable if the type of that variable is deduced (either via `auto` or template argument type deduction). `std::function` has a template constructor that is able to take a lambda argument via this principle of template argument type deduction. – Mankarse Nov 22 '16 at 06:36
  • 5
    Let's look at this: int b; b = 13; You declare names, not literals. How would you declare 13, and why? Your lambda is like 13 and `bar` is like `b`. – n. m. could be an AI Nov 22 '16 at 06:37
  • @Mankarse Interesting fact that I did not know. Thanks. – MxNx Nov 22 '16 at 06:39
  • 1
    By the way, the distinction is the same in C#. You appear to be confusing (C#) delegates with (C#) lambdas (essentially: 'anonymous delegate implementations'). In C#, lambdas can be assigned to delegates (variables) but they are not the same thing, just like in C++. – Euro Micelli Nov 22 '16 at 14:36

3 Answers3

44

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.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 1
    @Mzhr note that the type of a lambda is not std::function, but an unnamed type unique to the lambda. std::function is a polymorphic wrapper around any callable object. – Guillaume Racicot Nov 23 '16 at 02:17
9

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.

bashrc
  • 4,725
  • 1
  • 22
  • 49
  • Thanks for the detailed response. May I ask about the source of your quote? Is it from a C++ specification? – MxNx Nov 22 '16 at 06:46
9

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).

6502
  • 112,025
  • 15
  • 165
  • 265