working with functions in sol3

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

working with callables/lambdas

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


When you set lambdas/callables through my_table.set( ... ) using the same function signature, you can suffer from const static data (like string literals) from not “behaving properly”. This is because some compilers do not provide unique type names that we can get at in C++ with RTTI disabled, and thusly it will register the first lambda of the specific signature as the one that will be called. The result is that string literals and other data stored in an compiler implementation-defined manner might be folded and the wrong routine run, even if other observable side effects are nice.

To avoid this problem, register all your lambdas with my_table.set_function and avoid the nightmare altogether.

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:

#include <sol/sol.hpp>

#include <assert.hpp>

int main(int, char*[]) {

	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& from_lua_t1 = lua["t1"];
	test& from_lua_t2 = lua["t2"];

	// not the same: 'f' lambda copied
	c_assert(&from_lua_t1 != &t);
	// the same: 'g' lambda returned reference
	c_assert(&from_lua_t2 == &t);

	return 0;

exception safety/handling

All functions bound to sol3 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 sol3 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.

You can change this behavior by defining SOL_FUNCTION_CALL_VALUE_SEMANTICS, as defined in the safety configuration page. You can also change it for specific types using the sol::is_value_semantic_for_function<T> template and _partially specializing_ it to make either std::true_type, std::false_type, or equivalent true/false functionality.


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. sol3 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 sol3 has no mechanisms by which it can make this safe and not blow up in the user’s face.


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


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


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!