Possseidon / dang-lib

A C++ library, providing a variety of useful classes focused around game developement.
2 stars 1 forks source link

Fix invalid usage of lambdas with Lua bindings. #74

Open Possseidon opened 2 years ago

Possseidon commented 2 years ago

Pattern

The following pattern is heavily used in the current Lua bindings:

constexpr auto function = +[] {};
dlua::wrap<function>;

This is very convenient, as the full context of the class (for e.g. type aliases) is available inside the lambda.

Issue

This works fine on msvc and clang, but gcc gives an error, stating that the lambda cannot be used as it has no linkage - except it doesn't actually give this error at the moment. Everything compiles just fine with gcc... or does it?

Why it works (even though it shouldn't)

The reason it compiles is the (un)lucky coincidence of:

  1. Using gcc 9.x (any later version will likely break according to some testing on compiler-explorer)
  2. Having template stuff around the lambda.

Basically, upgrading gcc or moving the lambdas somewhere that isn't templated will (likely) cause the error.

Workarounds

There are several ways on how one can work around this issue.

1. Wrapping it in std::function

The probably easiest fix would be to simply type-erase the lambda with a std::function. This even has the benefit of having a nice signature when printing in Lua. However it comes with a decent performance hit.

2. Use free-standing functions

Simply replacing the lambdas with free-standing functions e.g. right in front of the function in which they are used is generally a good alternative. Context from the class is lost however.

3. Use static constexpr lambdas inside the class

This way, context of the class can be retained. Implementations have to be put in the header however, although that is hopefully not a big deal.

This also has the added benefit of allowing for re-use of wrappers between e.g. methods() and properties().

4. Proper static functions inside the class

Another option would be to use proper static functions, but implementing them is very verbose, as the entire template header has to be repeated for each and every function.

Conclusion

Use static constexpr lambdas (approach 3) like so:

Header

template <>
struct ClassInfo<Foo> {
    static std::array<luaL_Reg> methods();

private:
    static constexpr auto bar = +[] {};
};

Source

template <>
std::array<luaL_Reg> ClassInfo<Foo>::methods()
{
    return {reg<bar>("bar")};
}
Possseidon commented 2 years ago

If I'm not mistaken, C++20 allows passing lambdas as template parameters, which could not only fix this issue, but also allows me to get rid of the + before each lambda. The big question is, if GCC allows this or if this is still not possible because of the same reasons as previously (no linkage, which GCC might technically be correct about, but not sure).

Of course, the lambdas still won't be allowed to capture anything, but the conversion to a function pointer can happen inside the wrap style functions.