ThePhD / sol2

Sol3 (sol2 v3.0) - a C++ <-> Lua API wrapper with advanced features and top notch performance - is here, and it's great! Documentation:
http://sol2.rtfd.io/
MIT License
4.18k stars 515 forks source link

Binding functions with nonconst lvalue reference argument #1470

Closed izyakacman closed 1 year ago

izyakacman commented 1 year ago

Hello! Could you help me with this issue? I have a function like this void foo(int&) and I want to bind this function to the sol library, but I cannot.

void foo(int&){}
sol::state lua;
lua.set("foo", foo);

This code isn`t compiled. What am I doing wrong?

Rochet2 commented 1 year ago

Lua does not have input parameters support. How should the integer reference parameter be used?

One way to handle reference parameters is to have a parameter and a return value. You can achieve this by wrapping the function call like this:

#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

void foo(int& v) { v += 1; }

int main() {
    sol::state lua;
    lua.open_libraries();
    lua.set("foo", [](int v) { foo(v); return v; });
    lua.script("print(foo(1))");
    return 0;
}

You can also use tables to wrap your value. This makes it possible to edit it and see the effects in lua without returning the value, similar to input parameters.

// Define a C++ function that takes a Lua table as input
void foo_wrap(sol::table t) {
  int v = t["v"];
  foo(v);
  t["v"] = v;
}

// Register the function with sol2
lua.set_function("foo", &foo_wrap);

// Call the function from Lua, passing a table as an argument
local t = { v = 1 }
foo(t)
print(t.v) -- Output: 2

Similarily, you can also use a wrapper struct like this. You can make the code more generic and easier to use through template metaprogramming. By making the wrapper accessible from lua, you can also access the value in it from lua. In this example the value is just printed from Cpp

#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
#include <iostream>

void foo(int& v) { v += 1; }

// Define a C++ struct that holds an int reference
struct IntWrapper {
  int& value;
};

// Define a C++ function that takes an IntWrapper struct as input
void foo_wrapper(IntWrapper wrapper) {
  foo(wrapper.value);
}

int main() {
    sol::state lua;
    lua.open_libraries();

    // Create an int variable in C++
    int myInt = 1;

    // Create an IntWrapper object that references myInt
    IntWrapper myWrapper{ myInt };

    // Register the function and wrapper object with sol2
    lua.set_function("foo", &foo_wrapper);
    lua.set("myWrapper", &myWrapper);

    // Call the function from Lua, passing the wrapper object as an argument
    lua.script("foo(myWrapper)");

    // Check the value of myInt after the Lua script is executed
    std::cout << myInt << std::endl; // Output: 2
    return 0;
}
izyakacman commented 1 year ago

Thank you for your help. I tried one more variant:

struct A
{
     int value;
};
void foo(A&){}
lua["foo"] = foo;

This code is compiled but I used reference in this case too. First time I used internal type (int) and now I am using user type (A). Could you explain difference between these two cases? Why first one doesn`t work and second one works?

Rochet2 commented 1 year ago

When in lua, A is represented as userdata that references the original A object that you create. So you can modify the A object. However, the integer value is a primitive value in lua. You cannot/should not access the internal value that lua holds for it.

Here is how I think about it. In lua there are userdata, tables, integers, strings etc. If I have a function f(x), then if I pass values of different type to this function as the parameter x, can the function modify them? It can modify userdata and tables. But it cannot modify integers or strings. So userdata and tables are passed by reference. All references to the same table or userdata can modify the same object and you can see the side effects everywhere. However, with the primitive values like integers, strings, and booleans, you cannot see the side effects and they are copied when passed to functions.

The error likely tries to protect you from trying to have input parameters with primitive values that do not work in lua.

izyakacman commented 1 year ago

I have understood it. Thank you for your explanation.