Search code examples
c++lambdac++20

Lambda variable shared between translation units


I have a variable that I share in a header file. It is initialized with a lambda. I wish to call a function from another translation unit, taking this lambda variable as a parameter.

With gcc, I get an error:

used but never defined [-fpermissive].

Here is a cut down example:

// in shared header file
//
const auto make_lambda() { return [](){ }; }
inline const auto lambda = make_lambda();

// in cpp file
//
void func(const decltype(lambda)&);

int main() {
    func(lambda);
}

My understanding is that the lambda type should be shared between translation units.

When I alter the lambda variable to the following, the issue goes away:

inline const auto lambda = [](){ }; // <-- this does work

Solution

  • Clang provides an excellent diagnostic that explains the problem with make_lambda well:

    <source>:8:6: error: function 'func' is used but not defined in this translation unit,
                         and cannot be defined in any other translation unit because its type
                         does not have linkage
    void func(const decltype(lambda)&);
         ^
    

    Having "no linkage" means:

    When a name has no linkage, the entity it denotes cannot be referred to by names from other scopes.

    - [basic.link]/2.4

    The reason why our lambda has no linkage is that the closure type it defines is defined inside of make_lambda. This results in:

    Names not covered by these rules have no linkage. Moreover, except as noted, a name declared at block scope has no linkage.

    - [basic.link]/7

    The type you're trying to use is make_lambda::__lambda essentially, and this cannot be used across different TUs.

    ODR issues with sharing lambda expressions

    inline const auto lambda = [](){ };
    

    ... makes the compiler errors go away, but this makes your program ill-formed, no diagnostic required. We must respect the one-definition-rule, and we violate the following:

    In each such definition, except within the default arguments and default template arguments of D, corresponding lambda-expressions shall have the same closure type (see below).

    - [basic.def.odr]/14.6

    Every TU will have its own unique closure type, but lambda must have the same definition everywhere. This is an ODR violation.

    If you really insisted on doing it, the safe way would be to put it into a class type:

    struct dummy {
        using lambda_type = [] {};
    };
    

    dummy::lambda_type will have linkage and doesn't violate the ODR. See also: [basic.def.odr]/16

    Conclusion

    Just don't share lambdas across different TUs. Even if you make it work, the solution isn't pretty. Instead, consider:

    • writing a function template that accepts anything callable
    • accepting a function pointer
    • accepting std::function
    • creating a regular class, not a lambda, which has an overloaded call operator

    All of these options are much better than trying to share a lambda expression (outside of functions) across TUs.