Hello, everyone.
Very much enjoying sol2. I'm trying to wrap and expose an Eigen::MatrixXf to a lua script. It took me awhile to figure out a way that would compile, but I'm not sure if I'm doing this the best way. I was hoping I could get feedback.
class MatrixXf : Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>
{
public:
MatrixXf(int row, int col) :
Eigen::MatrixXf(row, col)
{
}
void resize(int row, int col)
{
Eigen::MatrixXf::resize(row, col);
}
};
...
void LuaLab::registerLuaExtensions(sol::state& lua)
{
....
lua.new_usertype<MatrixXf>(
"matrix",
sol::constructors<MatrixXf(int,int)>(),
"resize", &MatrixXf::resize
);
}
From a lua script:
m = matrix.new(5,5)
m:resize(2,2)
It works, but I'm wondering if I'm making things overly-complicated. Unfortunately, I've not found a way to 'typedef' or more directly wrap the Eigen template. For example, both of these fail to compile:
The error is the same for both modifications:
error C2440: 'initializing': cannot convert from 'initializer list' to 'luaL_Reg'
Is there a better way to register the usertype where I wouldn't have to replicate the entire API of the template?
Thanks!
Language: C++
MSVC 2017
You should be able to do something like this:
lua.new_usertype<Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>>("matrix",
sol::constructors<sol::types<Eigen::Index, Eigen::Index>>(),
"resize", &Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>::resize
);
lua.new_usertype<Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>> (
"eigen",
sol::constructors<sol::types<Eigen::DenseIndex, Eigen::DenseIndex>>()
);
Same error, but now it looks like it's the optional arguments in the constructor:
1> with
1> [
1> Class=Eigen::Matrix
1> ]
Is there a way to account for that?
What's the full error you're getting?
2>../sol.hpp(17142): error C2440: 'initializing': cannot convert from 'initializer list' to 'luaL_Reg'
2>../sol.hpp(17142): note: Invalid aggregate initialization
2>../sol.hpp(17191): note: see reference to function template instantiation 'void sol::usertype_detail::make_length_op
2> with
2> [
2> T=Eigen::Matrix
2> Regs=std::array
2> ]
2>../sol.hpp(17657): note: see reference to function template instantiation 'void sol::usertype_detail::insert_default_registrations
2> with
2> [
2> T=Eigen::Matrix
2> Regs=std::array
2> Fx=sol::usertype_metatable
2> ]
2>../sol.hpp(17655): note: while compiling class template member function 'int sol::usertype_metatable
2> with
2> [
2> T=Eigen::Matrix
2> ]
2>../sol.hpp(17915): note: see reference to function template instantiation 'int sol::usertype_metatable
2> with
2> [
2> T=Eigen::Matrix
2> ]
2>C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.11.25503\include\type_traits(477): note: see reference to class template instantiation 'sol::usertype_metatable
2> with
2> [
2> T=Eigen::Matrix
2> ]
2>C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.11.25503\include\memory(2150): note: see reference to class template instantiation 'std::is_convertible
2> with
2> [
2> T=Eigen::Matrix
2> ]
2>../sol.hpp(18679): note: see reference to function template instantiation 'sol::usertype
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>../sol.hpp(18679): note: see reference to function template instantiation 'sol::usertype
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>../sol.hpp(18684): note: see reference to function template instantiation 'sol::usertype
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>../sol.hpp(18684): note: see reference to function template instantiation 'sol::usertype
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>../sol.hpp(18695): note: see reference to function template instantiation 'sol::usertype
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>../sol.hpp(18695): note: see reference to function template instantiation 'sol::usertype
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>../sol.hpp(19205): note: see reference to function template instantiation 'sol::usertype
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>../sol.hpp(19205): note: see reference to function template instantiation 'sol::usertype
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>../sol.hpp(20365): note: see reference to function template instantiation 'sol::basic_table_core
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>../sol.hpp(20365): note: see reference to function template instantiation 'sol::basic_table_core
2> with
2> [
2> Class=Eigen::Matrix
2> ]
2>..(503): note: see reference to function template instantiation 'sol::state_view &sol::state_view::new_usertype
Oof this was tricky. So upon further inspection it looks like sol tries to automatically bind some stuff on the class based on some assumptions it makes, but not everything is type compatible so the compiler whines. On top of that, since resize is generic it has to be specialized too. After some trial and error I got it to work with the latest version of sol2:
typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> EigenMatrix;
namespace sol
{
namespace meta
{
namespace meta_detail
{
template <>
struct has_size_test<EigenMatrix>
{
public:
static const bool value = false;
};
}
}
}
void main()
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::package);
lua.new_usertype<EigenMatrix>("matrix",
sol::constructors<EigenMatrix(EigenMatrix::Index, EigenMatrix::Index)>(),
"resize", (void(EigenMatrix::*)(EigenMatrix::Index, EigenMatrix::Index)) &EigenMatrix::resize
);
lua.script("m = matrix.new(5, 6)");
EigenMatrix& m = lua["m"];
std::cout << "Cols: " << m.cols() << " - Rows: " << m.rows() << std::endl;
lua.script("m:resize(2, 3)");
std::cout << "Cols: " << m.cols() << " - Rows: " << m.rows() << std::endl;
}
Try it out and let me know if it works. Keep in mind that you might encounter other issues when binding other functions / variables.
@ThePhD do you think it would be possible to introduce some sort of trait that prevents sol from binding anything that's not explicitly specified in usertype definitions (if it doesn't already exist)?
Works. Thank you very much!
I suppose my next challenge is to return a reference to a value in the underlying matrix that the lua script can manipulate. The operator() overload returns a float reference that I can see in the lua script, but it doesn't change the value in the matrix if the value is changed in the lua script. I'm still working through the documentation, though.
Such a big help. Thank you very much.
No problem. I would leave the issue open until @ThePhD has a chance to respond.
Return types of primitive types as references cannot be changed in Lua. Lua does not know how to handle references/pointers to primitives and treat them regularly. I've thought about trying to create some kind of abstraction for them but it never pans out or holds up to scrutiny (e.g., float* would have to be pushed as a userdata. Any function that expects a number and does lua_tonumber on that argument (from the C API) or does lua_isnumber will invariably fail).
As for the issue here, I honestly did not expect Eigen to have such a problematic interface. Unfortunately, there's no magic I can implement that says "work with size except for Eigen".
I can maybe look at Eigen to see if I can produce a better has_size test...
Eigen's size() is just: inline Index size() const { return rows() * cols(); }, where Index is most likely std::ptrdiff_t.
Thanks for the heads up on return types.
So far the @OrfeasZ solution is working, although I'm not doing anything particularly complicated yet. If the problem is unique to Eigen, perhaps documenting the proper approach is the best solution. I'll be working on this the next few days and will happily turn in whatever working code I hammer out.
Again, thank you for all the help. If you need anything tested, let me know.
lua_CFunction test = sol::c_call<decltype(&EigenMatrix::size), &EigenMatrix::size>;
This line doesn't compile. For some reason. It seems like I can't use Eigen's size in a compile-time context.
Yeah, it's pretty odd. I tried making a reproduction case by re-implementing size one-to-one but that didn't trigger the issue. I wonder if this also happens with other compilers.
Does it help if I post what I have working so far?
I should have already fixed it and Eigen should work properly now. Check latest.
@OrfeasZ There's also a note in the documentation for an escape hatch, when you don't want default functions automatically registered: http://sol2.readthedocs.io/en/latest/api/usertype.html#automagical-usertypes
Most helpful comment
I should have already fixed it and Eigen should work properly now. Check latest.
@OrfeasZ There's also a note in the documentation for an escape hatch, when you don't want default functions automatically registered: http://sol2.readthedocs.io/en/latest/api/usertype.html#automagical-usertypes