functions

working with functions in sol2

There are a number of examples dealing with functions and how they can be bound to sol2:

working with callables/lambdas

To be explicit about wanting a struct to be interpreted as a function, use mytable.set_function( key, func_value );. You can be explicit about wanting a function as well by using the sol::as_function call, which will wrap and identify your type as a function.

Note

As of sol 2.18.1, the below

Note

Function objects obj – a struct with a return_type operator()( ... ) member defined on them, like all C++ lambdas – are not interpreted as functions when you use set for mytable.set( key, value ) and state.create_table(_with)( ... ). This only happens automagically with mytable[key] = obj.

Note that this also applies to calling functions, for example: my_state["table"]["sort"]( some_table, sorting_object );.

Furthermore, it is important to know that lambdas without a specified return type (and a non-const, non-reference-qualified auto) will decay return values. To capture or return references explicitly, use decltype(auto) or specify the return type exactly as desired:

#define SOL_CHECK_ARGUMENTS 1
#include <sol.hpp>

int main(int argc, char* argv[]) {

        struct test {
                int blah = 0;
        };

        test t;
        sol::state lua;
        lua.set_function("f", [&t]() {
                return t;
        });
        lua.set_function("g", [&t]() -> test& {
                return t;
        });

        lua.script("t1 = f()");
        lua.script("t2 = g()");

        test& lt1 = lua["t1"];
        test& lt2 = lua["t2"];

        assert(&lt1 != &t); // not the same: 'f' lambda copied
        assert(&lt2 == &t); // the same: 'g' lambda returned reference

        return 0;
}

exception safety/handling

All functions bound to sol2 set up an exception trampoline around the function (unless you are working with a raw lua_CFunction you pushed yourself). protected_function also has an error handler member and an exception trampoline around its internals, but it is not guaranteed safe if an exception bubbles outside of it. Catching that exception is not safe either: if an exception has exploded out from the sol2 API somehow, you must assume the VM is in some indeterminate and/or busted state.

Please read the error page and exception page for more details about what to do with exceptions that explode out from the API.

functions and argument passing

All arguments are forwarded. Unlike get/set/operator[] on sol::state or sol::table, value semantics are not used here. It is forwarding reference semantics, which do not copy/move unless it is specifically done by the receiving functions / specifically done by the user.

Note

This also means that you should pass and receive arguments in certain ways to maximize efficiency. For example, sol::table, sol::object, sol::userdata and friends are cheap to copy, and should simply by taken as values. This includes primitive types like int and double. However, C++ types – if you do not want copies – should be taken as const type& or type&, to save on copies if it’s important. Note that taking references from Lua also means you can modify the data inside of Lua directly, so be careful. Lua by default deals with things mostly by reference (save for primitive types).

When you bind a function to Lua, please take any pointer arguments as T*, unless you specifically know you are going to match the exact type of the unique/shared pointer and the class it wraps. sol2 cannot support “implicit wrapped pointer casting”, such as taking a std::shared_ptr<MySecondBaseClass> when the function is passed a std::shared_ptr<MyDerivedClass>. Sometimes it may work because the compiler might be able to line up your classes in such a way that raw casts work, but this is undefined behavior through and through and sol2 has no mechanisms by which it can make this safe and not blow up in the user’s face.

Note

Please avoid taking special unique_usertype arguments, by either reference or value. In many cases, by-value does not work (e.g., with std::unique_ptr) because many types are move-only and Lua has no concept of “move” semantics. By-reference is dangerous because sol2 will hand you a reference to the original data: but, any pointers stored in Lua can be invalidated if you call .reset() or similar on the core pointer. Please take a pointer (T*) if you anticipate nil/nullptr being passed to your function, or a reference (const T& or T&) if you do not. As a side note, if you write a small wrapping class that holds a base pointer type, and interact using the wrapper, then when you get the wrapper as an argument in a C++-function bound to Lua you can cast the internal object freely. It is simply a direct cast as an argument to a function that is the problem.

Note

You can get even more speed out of sol::object style of types by taking a sol::stack_object (or sol::stack_..., where ... is userdata, reference, table, etc.). These reference a stack position directly rather than cheaply/safely the internal Lua reference to make sure it can’t be swept out from under you. Note that if you manipulate the stack out from under these objects, they may misbehave, so please do not blow up your Lua stack when working with these types.

std::string (and std::wstring) are special. Lua stores strings as const char* null-terminated strings. std::string will copy, so taking a std::string by value or by const reference still invokes a copy operation. You can take a const char*, but that will mean you’re exposed to what happens on the Lua stack (if you change it and start chopping off function arguments from it in your function calls and such, as warned about previously).

function call safety

You can have functions here and on usertypes check to definitely make sure that the types passed to C++ functions are what they’re supposed to be by adding a #define SOL_CHECK_ARGUMENTS before including Sol, or passing it on the command line. Otherwise, for speed reasons, these checks are only used where absolutely necessary (like discriminating between overloads). See safety for more information.

raw functions (lua_CFunction)

When you push a function into Lua using Sol using any methods and that function exactly matches the signature int( lua_State* );, it will be treated as a raw C function (a lua_CFunction). This means that the usual exception trampoline Sol wraps your other function calls in will not be present. You will be responsible for catching exceptions and handling them before they explode into the C API (and potentially destroy your code). Sol in all other cases adds an exception-handling trampoline that turns exceptions into Lua errors that can be caught by the above-mentioned protected functions and accessors.

Note that stateless lambdas can be converted to a function pointer, so stateless lambdas similar to the form [](lua_State*) -> int { ... } will also be pushed as raw functions. If you need to get the Lua state that is calling a function, use sol::this_state.

Warning

Do NOT assume that building Lua as C++ will allow you to throw directly from a raw function. If an exception is raised and it bubbles into the Lua framework, even if you compile as C++, Lua does not recognize exceptions other than the ones that it uses with lua_error. In other words, it will return some completely bogus result, potentially leave your Lua stack thrashed, and the rest of your VM can be in a semi-trashed state. Please avoid this!