Sol2: Containers of pointers to Abstract Classes don't work.

Created on 9 Aug 2017  Â·  11Comments  Â·  Source: ThePhD/sol2

I have 3 classes, Base, Derived, and Container. All three are to be registered as user_types through sol, with Base being marked no_constructor and providing methods to cast appropriately.

Base is an abstract class that defines an interface, and Derived satisfies it.

Container is a Standards Compliantâ„¢ Container of unique pointers to Base.

When sol::is_container<Container>::value is recognized as true, i get compilation errors, detailed below.

Is this not possible with Sol2?

2>C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\include\xmemory0(840): error C2280: 'std::unique_ptr<Base,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)': attempting to reference a deleted function
2>        with
2>        [
2>            _Ty=Base
2>        ]
2>C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\include\memory(1857): note: see declaration of 'std::unique_ptr<Base,std::default_delete<_Ty>>::unique_ptr'
2>        with
2>        [
2>            _Ty=Base
2>        ]
2>sol.hpp(6467): note: see reference to function template instantiation 'void std::allocator<std::unique_ptr<Base,std::default_delete<_Ty>> *>::construct<std::unique_ptr<_Ty,std::default_delete<_Ty>>,std::unique_ptr<_Ty,std::default_delete<_Ty>>&>(_Objty *,std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' being compiled
2>        with
2>        [
2>            _Ty=Base,
2>            _Objty=std::unique_ptr<Base,std::default_delete<Base>>
2>        ]
2>sol.hpp(6467): note: see reference to function template instantiation 'void std::allocator<std::unique_ptr<Base,std::default_delete<_Ty>> *>::construct<std::unique_ptr<_Ty,std::default_delete<_Ty>>,std::unique_ptr<_Ty,std::default_delete<_Ty>>&>(_Objty *,std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' being compiled
2>        with
2>        [
2>            _Ty=Base,
2>            _Objty=std::unique_ptr<Base,std::default_delete<Base>>
2>        ]
2>sol.hpp(6699): note: see reference to function template instantiation 'void sol::detail::default_construct::construct<std::unique_ptr<Base,std::default_delete<_Ty>>*&,std::unique_ptr<_Ty,std::default_delete<_Ty>>&>(T,std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' being compiled
2>        with
2>        [
2>            _Ty=Base,
2>            T=std::unique_ptr<Base,std::default_delete<Base>> *&
2>        ]
2>sol.hpp(6685): note: see reference to function template instantiation 'int sol::stack::pusher<std::unique_ptr<Base,std::default_delete<_Ty>>,void>::push_deep<std::unique_ptr<_Ty,std::default_delete<_Ty>>&>(lua_State *,std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' being compiled
2>        with
2>        [
2>            _Ty=Base
2>        ]
2>sol.hpp(6685): note: see reference to function template instantiation 'int sol::stack::pusher<std::unique_ptr<Base,std::default_delete<_Ty>>,void>::push_deep<std::unique_ptr<_Ty,std::default_delete<_Ty>>&>(lua_State *,std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' being compiled
2>        with
2>        [
2>            _Ty=Base
2>        ]
2>sol.hpp(4789): note: see reference to function template instantiation 'int sol::stack::pusher<std::unique_ptr<Base,std::default_delete<_Ty>>,void>::push<std::unique_ptr<_Ty,std::default_delete<_Ty>>&,sol::meta::enable_t::_>(lua_State *,Arg)' being compiled
2>        with
2>        [
2>            _Ty=Base,
2>            Arg=std::unique_ptr<Base,std::default_delete<Base>> &
2>        ]
2>sol.hpp(4789): note: see reference to function template instantiation 'int sol::stack::pusher<std::unique_ptr<Base,std::default_delete<_Ty>>,void>::push<std::unique_ptr<_Ty,std::default_delete<_Ty>>&,sol::meta::enable_t::_>(lua_State *,Arg)' being compiled
2>        with
2>        [
2>            _Ty=Base,
2>            Arg=std::unique_ptr<Base,std::default_delete<Base>> &
2>        ]
2>sol.hpp(12642): note: see reference to function template instantiation 'int sol::stack::stack_detail::push_reference<std::unique_ptr<Base,std::default_delete<_Ty>>&,std::unique_ptr<_Ty,std::default_delete<_Ty>>&,>(lua_State *,Arg)' being compiled
2>        with
2>        [
2>            _Ty=Base,
2>            Arg=std::unique_ptr<Base,std::default_delete<Base>> &
2>        ]
2>sol.hpp(12630): note: while compiling class template member function 'int sol::container_usertype_metatable<Container,void>::real_index_call_associative(std::false_type,lua_State *)'
2>sol.hpp(12656): note: see reference to function template instantiation 'int sol::container_usertype_metatable<Container,void>::real_index_call_associative(std::false_type,lua_State *)' being compiled
2>sol.hpp(12974): note: see reference to class template instantiation 'sol::container_usertype_metatable<Container,void>' being compiled
2>sol.hpp(13008): note: see reference to function template instantiation 'std::array<luaL_Reg,12> sol::stack::stack_detail::container_metatable<T>(void)' being compiled
2>        with
2>        [
2>            T=Container
2>        ]
Maybe.Bug...?

Most helpful comment

Move-only types are still a no-go for the most part (you won't be able to use set and other related things).

But, this code now works:

````cpp

int main(int argc, char*argv[]) {

struct base_t {
    virtual int get() const = 0;
};

struct derived_1_t : base_t {
    virtual int get() const override {
        return 250;
    }
};

struct derived_2_t : base_t {
    virtual int get() const override {
        return 500;
    }
};

sol::state lua;
lua.open_libraries(sol::lib::base);
derived_1_t d1;
derived_2_t d2;

std::vector<std::unique_ptr<base_t>> v1;
v1.push_back(std::make_unique<derived_1_t>());
v1.push_back(std::make_unique<derived_2_t>());

std::vector<base_t*> v2;
v2.push_back(&d1);
v2.push_back(&d2);

lua["c1"] = std::move(v1);
lua["c2"] = &v2;

return 0;

}
````

All 11 comments

Seems like it's a problem with the container attempting to perform a copy somewhere it shouldn't. I'll see if I can drill down into why that's happening.

Alright, so the container needs to copy in many cases because when you iterate we need to dereference the iterator and pass the value into Lua. That means std::unique_ptr<Base>& val = *it;, and then sol::stack::push(lua_state, val);. Suffice to say, the default behavior for such a thing is an attempt at a copy, because val is an l-value reference (not a "temporary"/r-value reference &&).

So, this presents a conundrum. We very well can't copy. But what alternative do we have that will still "work" for other types? Do we forcefully dereference all pointer-like types and pass in a reference to the data? How will that work out in the end...?

I'm sure I'll come up with an answer soon or something.

I see

Any workarounds? It'd work if i just made Base not abstract, right? Not ideal, but in this case i can do it for now. I just tried that, still fails to compile. That means the issue is not with the abstractness, but all containers of unique pointers?

A little bit more testing reveals this is actually two issues. Containers of unique pointers don't work, and containers of raw pointers to an abstract class don't work.

For now i've managed to get it to compile by using a dereferencing iterator and making Base non-abstract. Not ideal, but seems to work for now.

I've fixed the issue of raw pointers to an abstract class: I was using the value_type directly somewhere and saving a value and that was causing issues. The only issue left is deciding how to handle an iterator that returns a move-only type.

Move-only types are still a no-go for the most part (you won't be able to use set and other related things).

But, this code now works:

````cpp

int main(int argc, char*argv[]) {

struct base_t {
    virtual int get() const = 0;
};

struct derived_1_t : base_t {
    virtual int get() const override {
        return 250;
    }
};

struct derived_2_t : base_t {
    virtual int get() const override {
        return 500;
    }
};

sol::state lua;
lua.open_libraries(sol::lib::base);
derived_1_t d1;
derived_2_t d2;

std::vector<std::unique_ptr<base_t>> v1;
v1.push_back(std::make_unique<derived_1_t>());
v1.push_back(std::make_unique<derived_2_t>());

std::vector<base_t*> v2;
v2.push_back(&d1);
v2.push_back(&d2);

lua["c1"] = std::move(v1);
lua["c2"] = &v2;

return 0;

}
````

Thanks, thats great!

For move only types, does just using a reference not work or not an option?

We return a reference in some cases, yes. But there's nothing we can do for container[1] = value, because Lua has no concept of "move" and we'd have to perform a copy. That code will error properly with that since we check for copy-assignability (and you'll get a descriptive error).

Some way to make it a read only container maybe?

It is read only. The error isn't a compile-time error: you'll get a Lua error you can read and inspect and catch with an error handler if you use a protected_function or safe_script.

Oh i see. Seems fine to me in that case

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sagunkho picture sagunkho  Â·  4Comments

morsisko picture morsisko  Â·  3Comments

squeek502 picture squeek502  Â·  3Comments

karenzshea picture karenzshea  Â·  4Comments

Erwsaym picture Erwsaym  Â·  4Comments