functions and You

sol can register all kinds of functions. Many are shown in the quick ‘n’ dirty, but here we will discuss many of the additional ways you can register functions into a sol-wrapped Lua system.

Setting a new function

Given a C++ function, you can drop it into sol in several equivalent ways, working similar to how setting variables works:

Registering C++ functions.
 1#define SOL_ALL_SAFETIES_ON 1
 2#include <sol/sol.hpp>
 3
 4std::string my_function(int D_count, std::string original) {
 5	// Create a string with the letter 'D' "D_count" times,
 6	// append it to 'original'
 7	return original + std::string(D_count, 'D');
 8}
 9
10int main() {
11
12	sol::state lua;
13
14	lua["my_func"] = my_function;             // way 1
15	lua.set("my_func", my_function);          // way 2
16	lua.set_function("my_func", my_function); // way 3
17
18	// This function is now accessible as 'my_func' in
19	// lua scripts / code run on this state:
20	lua.script("some_str = my_func(1, 'Da')");
21
22	// Read out the global variable we stored in 'some_str' in
23	// the quick lua code we just executed
24	std::string some_str = lua["some_str"];
25	SOL_ASSERT(some_str == "DaD");
26
27	return 0;
28}

The same code works with all sorts of functions, from member function/variable pointers you have on a class as well as lambdas:

Registering C++ member functions
 1#define SOL_ALL_SAFETIES_ON 1
 2#include <sol/sol.hpp>
 3
 4struct my_class {
 5	int a = 0;
 6
 7	my_class(int x) : a(x) {
 8	}
 9
10	int func() {
11		++a; // increment a by 1
12		return a;
13	}
14};
15
16int main() {
17
18	sol::state lua;
19	lua.open_libraries(sol::lib::base);
20
21	// Here, we are binding the member function and a class
22	// instance: it will call the function on the given class
23	// instance
24	lua.set_function(
25	     "my_class_func", &my_class::func, my_class(0));
26
27	// We do not pass a class instance here:
28	// the function will need you to pass an instance of
29	// "my_class" to it in lua to work, as shown below
30	lua.set_function("my_class_func_2", &my_class::func);
31
32	// With a pre-bound instance:
33	lua.script(R"(
34			first_value = my_class_func()
35			second_value = my_class_func()
36			assert(first_value == 1)
37			assert(second_value == 2)
38		)");
39
40	// With no bound instance:
41	lua.set("obj", my_class(24));
42	// Calls "func" on the class instance
43	// referenced by "obj" in Lua
44	lua.script(R"(
45			third_value = my_class_func_2(obj)
46			fourth_value = my_class_func_2(obj)
47			assert(third_value == 25)
48			assert(fourth_value == 26)
49		)");
50
51	return 0;
52}

Member class functions and member class variables will both be turned into functions when set in this manner. You can get intuitive variable with the obj.a = value access after this section when you learn about usertypes to have C++ in Lua, but for now we’re just dealing with functions!

Another question a lot of people have is about function templates. Function templates – member functions or free functions – cannot be registered because they do not exist until you instantiate them in C++. Therefore, given a templated function such as:

A C++ templated function
1template <typename A, typename B>
2auto my_add(A a, B b) {
3	return a + b;
4}

You must specify all the template arguments in order to bind and use it, like so:

Registering function template instantiations
 1}
 2
 3int main() {
 4
 5	sol::state lua;
 6
 7	// adds 2 integers
 8	auto int_function_pointer = &my_add<int, int>;
 9	lua["my_int_add"] = int_function_pointer;
10
11	// concatenates 2 strings
12	auto string_function_pointer
13	     = &my_add<std::string, std::string>;
14	lua["my_string_combine"] = string_function_pointer;
15
16	lua.script("my_num = my_int_add(1, 2)");
17	int my_num = lua["my_num"];
18	SOL_ASSERT(my_num == 3);
19
20	lua.script(
21	     "my_str = my_string_combine('bark bark', ' woof "
22	     "woof')");
23	std::string my_str = lua["my_str"];
24	SOL_ASSERT(my_str == "bark bark woof woof");
25
26	return 0;
27}

Notice here that we bind two separate functions. What if we wanted to bind only one function, but have it behave differently based on what arguments it is called with? This is called Overloading, and it can be done with sol::overload like so:

Registering C++ function template instantiations
 1#define SOL_ALL_SAFETIES_ON 1
 2#include <sol/sol.hpp>
 3
 4template <typename A, typename B>
 5auto my_add(A a, B b) {
 6	return a + b;
 7}
 8
 9int main() {
10
11	sol::state lua;
12
13	auto int_function_pointer = &my_add<int, int>;
14	auto string_function_pointer
15	     = &my_add<std::string, std::string>;
16	// adds 2 integers, or "adds" (concatenates) two strings
17	lua["my_combine"] = sol::overload(
18	     int_function_pointer, string_function_pointer);
19
20	lua.script("my_num = my_combine(1, 2)");
21	lua.script(
22	     "my_str = my_combine('bark bark', ' woof woof')");
23	int my_num = lua["my_num"];
24	std::string my_str = lua["my_str"];
25	SOL_ASSERT(my_num == 3);
26	SOL_ASSERT(my_str == "bark bark woof woof");
27
28	return 0;
29}

This is useful for functions which can take multiple types and need to behave differently based on those types. You can set as many overloads as you want, and they can be of many different types.

Note

Binding functions with default parameters (void func(int a = 1);) does not magically bind multiple versions of the function to be called with the default parameters. You must instead use sol::overload and bind the full version of the function, with lambdas or similar for the function calls one by one.

Note

please make sure to understand the implications of binding a lambda/callable struct in the various ways and what it means for your code!

Getting a function from Lua

There are 2 ways to get a function from Lua. One is with sol::unsafe_function and the other is a more advanced wrapper with sol::protected_function. As the names indicate, one is safer and allows you to introspect on errors during the function’s execution, while the other assumes that everything ran fine and simply provides the result (for an often negligible increase in speed).

You can use them to retrieve callables from Lua and call the underlying function, in two ways:

Retrieving a sol::(unsafe_/protected_)function
 1#define SOL_ALL_SAFETIES_ON 1
 2#include <sol/sol.hpp>
 3
 4int main() {
 5
 6	sol::state lua;
 7
 8	lua.script(R"(
 9			function f (a)
10				return a + 5
11			end
12		)");
13
14	// Get and immediately call
15	int x = lua["f"](30);
16	SOL_ASSERT(x == 35);
17
18	// Store it into a variable first, then call
19	sol::unsafe_function f = lua["f"];
20	int y = f(20);
21	SOL_ASSERT(y == 25);
22
23	// Store it into a variable first, then call
24	sol::protected_function safe_f = lua["f"];
25	int z = safe_f(45);
26	SOL_ASSERT(z == 50);
27
28	return 0;
29}

Note

sol::function is an alias for either sol::protected_function or sol::unsafe_function, depending on the configuration settings.

You can get anything that’s a callable in Lua, including C++ functions bound using set_function or similar. sol::protected_function behaves similarly to sol::unsafe_function, but has a set_error_handler() variable you can set to a Lua function. This catches all errors and runs them through the error-handling function:

Retrieving a sol::protected_function
 1int main () {
 2        sol::state lua;
 3
 4        lua.script(R"(
 5                function handler (message)
 6                        return "Handled this message: " .. message
 7                end
 8
 9                function f (a)
10                        if a < 0 then
11                                error("negative number detected")
12                        end
13                        return a + 5
14                end
15        )");
16
17        sol::protected_function f = lua["f"];
18        f.set_error_handler(lua["handler"]);
19
20        sol::protected_function_result result = f(-500);
21        if (result.valid()) {
22                // Call succeeded
23                int x = result;
24        }
25        else {
26                // Call failed
27                sol::error err = result;
28                std::string what = err.what();
29                // 'what' Should read
30                // "Handled this message: negative number detected"
31        }
32}

Multiple returns to and from Lua

You can return multiple items to and from Lua using std::tuple/std::pair classes provided by C++. These enable you to also use sol::tie to set return values into pre-declared items. To recieve multiple returns, just ask for a std::tuple type from the result of a function’s computation, or sol::tie a bunch of pre-declared variables together and set the result equal to that:

Multiple returns from Lua
 1int main () {
 2        sol::state lua;
 3
 4        lua.script("function f (a, b, c) return a, b, c end");
 5
 6        std::tuple<int, int, int> result;
 7        result = lua["f"](1, 2, 3);
 8        // result == { 1, 2, 3 }
 9        int a, int b;
10        std::string c;
11        sol::tie( a, b, c ) = lua["f"](1, 2, "bark");
12        // a == 1
13        // b == 2
14        // c == "bark"
15}

You can also return mutiple items yourself from a C++-bound function. Here, we’re going to bind a C++ lambda into Lua, and then call it through Lua and get a std::tuple out on the other side:

Multiple returns into Lua
 1int main () {
 2        sol::state lua;
 3
 4        lua["f"] = [](int a, int b, sol::object c) {
 5                // sol::object can be anything here: just pass it through
 6                return std::make_tuple( a, b, c );
 7        };
 8
 9        std::tuple<int, int, int> result = lua["f"](1, 2, 3);
10        // result == { 1, 2, 3 }
11
12        std::tuple<int, int, std::string> result2;
13        result2 = lua["f"](1, 2, "Arf?")
14        // result2 == { 1, 2, "Arf?" }
15
16        int a, int b;
17        std::string c;
18        sol::tie( a, b, c ) = lua["f"](1, 2, "meow");
19        // a == 1
20        // b == 2
21        // c == "meow"
22}

Note here that we use sol::object to transport through “any value” that can come from Lua. You can also use sol::make_object to create an object from some value, so that it can be returned into Lua as well.

Any return to and from Lua

It was hinted at in the previous code example, but sol::object is a good way to pass “any type” back into Lua (while we all wait for std::variant<...> to get implemented and shipped by C++ compiler/library implementers).

It can be used like so, inconjunction with sol::this_state:

Return anything into Lua
 1sol::object fancy_func (sol::object a, sol::object b, sol::this_state s) {
 2        sol::state_view lua(s);
 3        if (a.is<int>() && b.is<int>()) {
 4                return sol::make_object(lua, a.as<int>() + b.as<int>());
 5        }
 6        else if (a.is<bool>()) {
 7                bool do_triple = a.as<bool>();
 8                return sol::make_object(lua, b.as<double>() * ( do_triple ? 3 : 1 ) );
 9        }
10        return sol::make_object(lua, sol::lua_nil);
11}
12
13int main () {
14        sol::state lua;
15
16        lua["f"] = fancy_func;
17
18        int result = lua["f"](1, 2);
19        // result == 3
20        double result2 = lua["f"](false, 2.5);
21        // result2 == 2.5
22
23        // call in Lua, get result
24        lua.script("result3 = f(true, 5.5)");
25        double result3 = lua["result3"];
26        // result3 == 16.5
27}

This covers almost everything you need to know about Functions and how they interact with sol. For some advanced tricks and neat things, check out sol::this_state and sol::variadic_args. The next stop in this tutorial is about C++ types (usertypes) in Lua! If you need a bit more information about functions in the C++ side and how to best utilize arguments from C++, see this note.