Closed mattangus closed 2 years ago
After mucking about with binders source I've discovered that the return type is not bindable Eigen::Matrix
has isDependentType() == true
. So the return type and parameters are not bindable. Is this a bug? amat
is bound with no problem.
Actually scratch that. It's becuase Eigen::Matrix
has getPointOfInstantiation().isInvalid() == false
. Not quite sure how to change that. Any suggestions?
Just adding some debug print statements sheds a little light:
checking if FunctionDecl test::TestClass<float>::multiply is bindable
checking if QualType test::TestClass<float>::vector3_type is bindable
checking if CXXRecordDecl Eigen::Matrix is bindable
checking Eigen::Matrix
Eigen::Matrix point of instantiation is invalid
test::TestClass<float>::multiply return type is not bindable
@mattangus invalid point-of-instantiation usually mean that template type is not fully instantiated. Easiest way to "fix" this would be to add inline function that take this type by-value ie like void foo(MyTemplateType<A, B, C>) {}
.
Could you please try this and let me know how it goes? Thanks,
I'm not sure I understood correctly, but that didn't seem to work. Adding this to TestClass.hpp
doesn't cause multiply
to be generated as a member function.
void foo(TestClass<float>) {}
generates
void bind_TestClass(std::function< pybind11::module &(std::string const &namespace_) > &M)
{
{ // test::TestClass file:TestClass.hpp line:10
pybind11::class_<test::TestClass<float>, std::shared_ptr<test::TestClass<float>>> cl(M("test"), "TestClass_float_t", "");
cl.def( pybind11::init( [](){ return new test::TestClass<float>(); } ) );
cl.def( pybind11::init( [](test::TestClass<float> const &o){ return new test::TestClass<float>(o); } ) );
cl.def_readwrite("amat", &test::TestClass<float>::amat);
cl.def("scale", (void (test::TestClass<float>::*)(float)) &test::TestClass<float>::scale, "C++: test::TestClass<float>::scale(float) --> void", pybind11::arg("factor"));
}
{ // test::TestClassf file:TestClass.hpp line:35
pybind11::class_<test::TestClassf, std::shared_ptr<test::TestClassf>, test::TestClass<float>> cl(M("test"), "TestClassf", "");
cl.def( pybind11::init( [](){ return new test::TestClassf(); } ) );
}
// test::foo(struct test::TestClass<float>) file:TestClass.hpp line:37
M("test").def("foo", (void (*)(struct test::TestClass<float>)) &test::foo, "C++: test::foo(struct test::TestClass<float>) --> void", pybind11::arg(""));
}
It seems to be a problem with the template parameter for my class that is used to define the Eigen::Matrix
type. I changed this:
using vector2_type = Eigen::Matrix<T, 2, 1>;
using vector3_type = Eigen::Matrix<T, 3, 1>;
using matrix3_type = Eigen::Matrix<T, 3, 3>;
to this:
using vector2_type = Eigen::Matrix<float, 2, 1>;
using vector3_type = Eigen::Matrix<float, 3, 1>;
using matrix3_type = Eigen::Matrix<float, 3, 3>;
and the multiply
member function is now generated. It doesn't seem to be a limitation of binder because the binding for foo
is generated in this small example:
#pragma once
namespace test {
template <typename T>
class Inner {
private:
public:
Inner(T data) : data(data) {}
T data;
};
template <typename T>
class Outer {
private:
public:
Outer(T data) : inner(data) {}
Inner<T> inner;
void foo(Inner<T> d) {
d.data += 1;
}
};
class Innerf : public Inner<float> {};
class Outerf : public Outer<float> {};
} // namespace test
which generates:
void bind_NestedTemplate(std::function< pybind11::module &(std::string const &namespace_) > &M)
{
{ // test::Inner file:NestedTemplate.hpp line:7
pybind11::class_<test::Inner<float>, std::shared_ptr<test::Inner<float>>> cl(M("test"), "Inner_float_t", "");
cl.def( pybind11::init<float>(), pybind11::arg("data") );
cl.def_readwrite("data", &test::Inner<float>::data);
}
{ // test::Outer file:NestedTemplate.hpp line:16
pybind11::class_<test::Outer<float>, std::shared_ptr<test::Outer<float>>> cl(M("test"), "Outer_float_t", "");
cl.def( pybind11::init<float>(), pybind11::arg("data") );
cl.def_readwrite("inner", &test::Outer<float>::inner);
cl.def("foo", (void (test::Outer<float>::*)(class test::Inner<float>)) &test::Outer<float>::foo, "C++: test::Outer<float>::foo(class test::Inner<float>) --> void", pybind11::arg("d"));
}
{ // test::Innerf file:NestedTemplate.hpp line:27
pybind11::class_<test::Innerf, std::shared_ptr<test::Innerf>, test::Inner<float>> cl(M("test"), "Innerf", "");
}
{ // test::Outerf file:NestedTemplate.hpp line:30
pybind11::class_<test::Outerf, std::shared_ptr<test::Outerf>, test::Outer<float>> cl(M("test"), "Outerf", "");
}
}
So maybe there is something with Eigen that's making things more complex? In theory ther shouldn't be any difference between these two examples because Outer ~= TestClass
and Inner ~= Eigen::Matrix
.
Sorry for spamming you! I misenterpreted what you were saying. Adding this function:
void foo(TestClass<float>::vector2_type, TestClass<float>::matrix3_type, TestClass<float>::vector3_type) {}
made the multiply
function have bindings generated. My question is now: is there any other way to make a valid point of instantiation? This will be quite tedious especially for a matrix class. I will have to define throw away functions for each type and every combination of integers for the size of the matrix. Which will increase the binary size etc.
My question is now: is there any other way to make a valid point of instantiation? This will be quite tedious especially for a matrix class. I will have to define throw away functions for each type and every combination of integers for the size of the matrix. Which will increase the binary size etc.
-- i do not think there is a better way to handle this at least not at the moment.
Re binary size: - well, if we want bindings for particular template (with particular parameters) then we have to instantiate such classes and compile them in advance, - there is simply no other way. This is trade-off between the speed and binary sizes. If you really care about binary sizes and want smallest binary (and smallest memory footprint) then it would be best to use non-template matrix library in such case.
I'm not super worried about the binary size, it was just an example of a limitation of requiring a valid point of instantiation for a template that can have integral values.
Thinking about it a bit more wouldn't the generated code force the valid point of instantiation? Requiring it before the binding code has been generated seems like it's in the wrong order.
Thinking about it a bit more wouldn't the generated code force the valid point of instantiation? Requiring it before the binding code has been generated seems like it's in the wrong order.
-- problem is that in order to generate binding code (which will indeed trigger instantiation) we need to have valid point-of-instantiation so LLVM could provide Binder with information about our template class (it can not do this unless class is fully instantiated). Also please note that we need our class to be instantiated when we parse our all-includes.hpp (not when we already compiling our generated bindings).
Hope this helps,
That makes sense! Thanks!
In case anyone else has the same problem the best work around I found was to create a single function with variabels that require a point of instantiation. It can also be moved outside the namespace that is being bound to, or in an ignored namespace. Like so:
#pragma once
#include <iostream>
#include <Eigen/Dense>
namespace test {
template <typename T>
struct TestClass
{
//! @brief Types.
using scalar_type = T;
using vector2_type = Eigen::Matrix<T, 2, 1>;
using vector3_type = Eigen::Matrix<T, 3, 1>;
using matrix3_type = Eigen::Matrix<T, 3, 3>;
matrix3_type amat;
inline auto multiply(const vector2_type& x) const -> vector3_type
{
return amat * x.homogeneous();
}
inline auto mul2(const vector2_type x) const -> void {
std::cout << x.transpose() << std::endl;
}
inline auto scale(T factor) -> void
{
amat.block(0, 0, 2, 3) /= factor;
}
};
// is there a better way to do this?
class TestClassf : public TestClass<float> {};
} // namespace test
void foo() {
Eigen::Matrix<T, 2, 1> v1;
Eigen::Matrix<T, 3, 1> v2;
Eigen::Matrix<T, 3, 3> v3;
}
Macros could also be used to make for less repetition. This also doesn't seem to increase the binary size at all.
Thanks for putting this all together!
I am seeing some methods of a class being skipped. Binder doesn't seem to tell me why. Here is a minimal example:
CMakeLists.txt
config.cfg
TestClass.hpp
all_includes.hpp
Running
cmake ..
from build generates the following code:As you can see the
multiply
method is not included in the generated code. Presumably it's something to do with Eigen. Any idea why?It would be nice to have more levels of verbosity (or a
--explain
arg). Ideally this kind of skipping would be explained by binder (unless this is a bug).You can also download the above files here: test_binder.zip.