sol2 icon indicating copy to clipboard operation
sol2 copied to clipboard

Usertype inheritance seems to need open_libraries(sol::lib:base)

Open ernestum opened this issue 7 months ago • 3 comments

I was trying to get inheritance to work on usertypes and bumped into this weird behavior when playing with the example from the documentation: When you forget to open the sol::lib::base library, the dependency resolution stops working. As the documentation does not include the library import, it seems broken?

#include <sol/sol.hpp>

struct A { 
	int a = 10;
	virtual int call() { return 0; }
	virtual ~A(){}
};
struct B : A {
	int b = 11; 
	virtual int call() override { return 20; } 
};

int main (int, char*[]) {
    sol::state lua;
    lua.open_libraries(sol::lib::base); // remove this and it breaks!

    lua.new_usertype<B>( "A",
    	"call", &A::call
    );
    
    lua.new_usertype<B>( "B",
    	"call", &B::call,
    	sol::base_classes, sol::bases<A>()
    );

    lua["my_a"] = A{};
    lua["my_b"] = B{};

    sol::object b_out = lua["my_b"];

    std::cout << "A is A: " << lua.get<std::optional<A>>("my_a").has_value() << std::endl;
    std::cout << "A is B: " << lua.get<std::optional<B>>("my_a").has_value() << std::endl;
    std::cout << "B is A: " << lua.get<std::optional<A>>("my_b").has_value() << std::endl;
    std::cout << "B is B: " << lua.get<std::optional<B>>("my_b").has_value() << std::endl;
    
	return 0;
}

See this godbolt demo

Expected output is

A is A: 1
A is B: 0
B is A: 1
B is B: 1

But when I remove the library input I get:

A is A: 1
A is B: 0
B is A: 0
B is B: 1

In my production code it fails in spite of having sol::lib::base opened. But that one uses templated usertypes ...

ernestum avatar Apr 30 '25 08:04 ernestum

Ah now it is a typo in the example code from the documentation (and that typo made it via copy-paste to the above code):

In the first usertype declaration, the template parameter is B, while it should be A. When fixing the typo it works:

See godbolt

ernestum avatar Apr 30 '25 09:04 ernestum

Also, reading the shared ptr inheritance example, I would expect this code to work:

#include <sol/sol.hpp>

struct A { 
	int a = 10;
	virtual int call() { return 0; }
	virtual ~A(){}
};
struct B : A {
	int b = 11; 
	virtual int call() override { return 20; } 
};

int main (int, char*[]) {

    sol::state lua;
    lua.open_libraries(sol::lib::base);  // If it's here or not, makes no difference
 
    lua.new_usertype<A>( "A",
        "call", &A::call
    );

    lua.new_usertype<B>( "B",
        "call", &B::call,
        sol::base_classes, sol::bases<A>()
    );
  
    lua["my_a"] = A{};
    lua["my_b"] = B{};

    std::cout << "A is A: " << lua.get<std::optional<std::shared_ptr<A>>>("my_a").has_value() << std::endl;
    std::cout << "A is B: " << lua.get<std::optional<std::shared_ptr<B>>>("my_a").has_value() << std::endl;
    std::cout << "B is A: " << lua.get<std::optional<std::shared_ptr<A>>>("my_b").has_value() << std::endl;
    std::cout << "B is B: " << lua.get<std::optional<std::shared_ptr<B>>>("my_b").has_value() << std::endl;
    
    return 0;
}

godbolt but the output is just

A is A: 0
A is B: 0
B is A: 0
B is B: 0

ernestum avatar Apr 30 '25 09:04 ernestum

Ok this works with the changes:

  1. I use std::make_shared to create the objects
  2. The undocumented macros SOL_BASE_CLASSES/SOL_DERIVED_CLASSES are used to define the inheritance structure
#include <sol/sol.hpp>

struct A { 
	int a = 10;
	virtual int call() { return 0; }
	virtual ~A(){}
};
struct B : A {
	int b = 11; 
	virtual int call() override { return 20; } 
};

SOL_BASE_CLASSES(B, A);
SOL_DERIVED_CLASSES(A, B);

int main (int, char*[]) {

	sol::state lua;
    lua.open_libraries(sol::lib::base);  // If it's here or not, makes no difference
 
	lua.new_usertype<A>( "A",
		"call", &A::call
	);

	lua.new_usertype<B>( "B",
		"call", &B::call
	);

    lua["my_a"] = std::make_shared<A>();
    lua["my_b"] = std::make_shared<B>();

    std::cout << "A is A: " << lua.get<std::optional<std::shared_ptr<A>>>("my_a").has_value() << std::endl;
    std::cout << "A is B: " << lua.get<std::optional<std::shared_ptr<B>>>("my_a").has_value() << std::endl;
    std::cout << "B is A: " << lua.get<std::optional<std::shared_ptr<A>>>("my_b").has_value() << std::endl;
    std::cout << "B is B: " << lua.get<std::optional<std::shared_ptr<B>>>("my_b").has_value() << std::endl;
    
	return 0;
}

goodbolt

ernestum avatar Apr 30 '25 09:04 ernestum