Search code examples
c++lambdastaticc++17header-files

How to split static lambda in declaration and definition?


I have a static lambda inside a class, that is declared and defined in a header file as:

class A final {
    // inline could be removed of course for splitting
    static inline auto foo = [](auto& param1, auto& param2 auto& param3) {
        // do stuff
        return;
    }
}
// compiles fine

With static variables such as static int x = 42 I can split declaration and definition like this:

// bar.h:
class Bar {
    static int x;
}
// bar.cpp:
#include "bar.h"
int Bar::x = 42;

How can the same thing be archieved with above lambda? Changing the signature would be OK of course.


Solution

  • The basic problem is that every lambda expression has its own separate type (see also this answer). Since you need to know the type to declare a variable and you need to know the lambda expression to know its type, there is no way to declare a variable to hold the result of a lambda expression without knowing the lambda expression itself.

    It is possible to declare a variable to hold your lambda and define the variable separately as long as you know the lambda expression in both places. For example:

    inline auto makeFoo() {
        return [](auto& param1, auto& param2, auto& param3) {
            // do stuff
            return;
        };
    }
    
    class A final {
        static decltype(makeFoo()) foo;
    };
    
    decltype(makeFoo()) A::foo = makeFoo();
    

    However, it is impossible to separate the declaration of the variable from the lambda expression (i.e., you can't have the lambda expression only in the file where you put the definition of the variable).

    A lambda expression that does not capture anything can be converted to a pointer to a function. If you don't need your lambda to capture anything (like in your example) and only needed a callable for one specific signature, you could simply declare A::foo to be of function pointer type and initialize the definition of A::foo with a matching lambda:

    class A final {
        static void (*foo)(int&, float&, double&);
    };
    
    void (*A::foo)(int&, float&, double&) = [](auto& param1, auto& param2, auto& param3) {
        // do stuff
        return;
    };
    

    If you really need a generic lambda (one with auto parameters) like suggested by your example, that won't work either and you're most likely out of luck. Having a generic call operator means that the operator () function of your closure type must be a function template. Having an operator () function template means that its definition must be known for anyone to actually make a call. Even if you wrote your own class instead of using a lambda expression, there would be no way to have anyone call a generic operator () without knowing its definition. All you could do is declare explicit instantiations of the operator () template for all the signatures you need to support and define those separately. But that, again, requires that you actually know in advance which concrete signatures you need your callable to support…