Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation about sol::function parameter passing misleading #1670

Open
Spacechild1 opened this issue Feb 11, 2025 · 0 comments
Open

Documentation about sol::function parameter passing misleading #1670

Spacechild1 opened this issue Feb 11, 2025 · 0 comments

Comments

@Spacechild1
Copy link

Spacechild1 commented Feb 11, 2025

Here's a thing that recently bit me. https://sol2.readthedocs.io/en/latest/functions.html#functions-and-argument-passing says the following about function 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.

But this is not entirely correct. If you pass an object to a sol::function by const-reference, the object is copied!

The following table compares the behavior of function calls with table setters:

Type function call table setter
T& reference copy
const T& copy copy
(const) T* reference reference
T&& move move

While the table setter is consistent in its treatment of reference arguments, function calls treat const T& differently from T&. Looking at the source code, this seems to be done on purpose, but I'm not really sure I understand the rationale. I would have expected both to be passed by reference! Anyway, the document should reflect the actual behavior.

Here's some code that demonstrates the behavior:

#include <sol/sol.hpp>

#include <iostream>

struct Foo {
    float x = 0;
    float y = 0;

    Foo() {
        std::cout << "Foo::Foo()\n";
    }

    Foo(float x_, float y_)
        : x(x_), y(y_) {
        std::cout << "Foo::Foo(float, float)\n";
    }

    ~Foo() {
        std::cout << "Foo::~Foo()\n";
    }

    Foo(const Foo& other)
        : x(other.x), y(other.y) {
        std::cout << "Foo::Foo(const Foo&)\n";
    }

    Foo(Foo&& other)
        : x(other.x), y(other.y) {
        std::cout << "Foo::Foo(Foo&&)\n";
    }

    Foo& operator=(const Foo& other) {
        std::cout << "Foo::operator=(const Foo&)\n";
        x = other.x;
        y = other.y;
        return *this;
    }

    Foo& operator=(Foo&& other) {
        std::cout << "Foo::operator=(Foo&&)\n";
        x = other.x;
        y = other.y;
        return *this;
    }
};

int main(int argc, const char** argv)
{
    sol::state state;
    state.open_libraries();

    state.new_usertype<Foo>(
        "Foo",
        "x", &Foo::x,
        "y", &Foo::y
    );

    state.script(R"(
function takeFoo(foo)
    print("passFoo:", foo.x, foo.y)
    return foo
end
    )");

    {
        std::cout << "a. function call:\n";
        std::cout << "\n";

        Foo foo(3, 4);
        std::cout << "\n";

        std::cout << "#1 non-const ref\n";
        // 'foo' is passed by reference!
        state["takeFoo"](foo);
        std::cout << "\n";

        std::cout << "#2 const ref\n";
        // 'foo' is copied!!
        state["takeFoo"](static_cast<const Foo&>(foo));
        std::cout << "\n";

        std::cout << "#3 pointer\n";
        // 'foo' is passed by reference/pointer
        state["takeFoo"](&foo);
        std::cout << "\n";

        std::cout << "#4 rvalue ref\n";
        // 'foo' is moved
        state["takeFoo"](std::move(foo));
        std::cout << "\n";
    }

    std::cout << "---\n";
    std::cout << "\n";

    {
        std::cout << "b. table setter:\n";

        Foo foo(3, 4);
        std::cout << "\n";

        std::cout << "#1 non-const ref\n";
        // 'foo' is copied
        state["x"] = foo;
        std::cout << "\n";

        std::cout << "#2 const ref\n";
        // 'foo' is copied
        state["x"] = static_cast<const Foo&>(foo);
        std::cout << "\n";

        std::cout << "#3 pointer\n";
        // 'foo' is passed by reference/pointer
        state["x"] = static_cast<const Foo*>(&foo);
        std::cout << "\n";

        std::cout << "#4 rvalue ref\n";
        // 'foo' is moved
        state["x"] = std::move(foo);
        std::cout << "\n";
    }

    std::cout << "---\n";
    std::cout << "\n";

    std::cout << "done!\n";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant