-
Notifications
You must be signed in to change notification settings - Fork 195
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
[BUG]: Handling of containers (e.g. std::vector) of Eigen::Ref<const T> #682
Comments
Is this an issue that nanobind can actually fix? If not, it might be better to include a |
I think there could be a workaround where the temp storage is managed by Eigen::Ref's type caster itself, which should be kept alive (we can gate this at compile-time behind a check for the version of Eigen, the next release of Eigen won't need that). This means other casters (e.g. optional_caster or the list_caster) shouldn't create the inner caster on the fly like here but always store them as data members. |
The change you propose to make casters store their member by default are likely impossible because this breaks creation of containers that store references (and it would in any case be too intrusive of a change, if "only" justified by the Eigen type caster). Perhaps a good stop-gap solution would be to raise a compile-time error message and ask users to upgrade their version of Eigen? If you agree, could you create a PR that adds such an error message? |
Hm, you are right it would be intrusive and create tons of problems, and looking back to how pybind11 works, it didn't do that, either.
Unfortunately I don't think this could work. Lots of things depend on Eigen and would need consistent versions of it for interop, and the move ctor is still on the master branch (with no release in sight yet, I've asked around on the Discord). Over the past week, what I've done instead was check how pybind11 did it, which is through managing a copy (a numpy copy created in C++ and managed with pybind's life support mechanism). |
I see that
A more compact and local (but untested) alternative that comes to my mind could be: derive a struct from #if EIGEN_VERSION <= 3.4.0
template<class CRef_>
struct MovableCRef : CRef_
{
using CRef_::CRef_;
MovableCRef::MovableCRef(CRef_&& that)
{
this->m_object = std::move(that.m_object);
}
MovableCRef::MovableCRef(MovableCRef&& that)
{
this->m_object = std::move(that.m_object);
}
};
template<class Ref_>
using MovableRef = std::conditional_t<Ref_::is_const, MovableCRef<Ref_>, Ref_>;
#else
template<class Ref_>
using MovableRef = Ref_;
#endif And use/return MovableRef whereever |
I agree, the changes are considerable because they aim to reproduce the workaround that pybind11 uses, but I'm sure an alternative is possible.
Actually this is a great suggestion. I'll try this in a new branch on my fork and see if/how it works. |
@WKarel I tried your suggestion. Unfortunately it would require intrusive changes to The problem here is on this line:
In this context, the template parameters are List = std::vector<Eigen::Ref<const T>> , Entry = Eigen::Ref<const T> , and the called function will be vector::push_back(const Entry&) (the copy version since there is no move ctor here and since the list caster is not aware the object is a actually a MovableCRef ).
|
FYI, this could perhaps be fixed by changing |
I tried this, changing the cast operator to This wouldn't change anything anyway, because at the call site for the cast operator in |
I have tried other things but I don't think we can avoid having to do the same trick as pybind11 - creating our own temp memory and tying it to the cleanup list. However I think we can gate this behaviour behind a check for a version of Eigen higher or equal to the current master branch (3.4.90). What do you think @WKarel ? |
I just tried it, and my idea does not work with STL containers using Visual C++, because e.g. std::vector::push_back uses std::allocator::construct, which copy-constructs its argument, since But apart from how to bind Using Eigen's current release: using MatXdC = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
using MatXdR = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
std::vector<Eigen::Ref<const MatXdR>> v;
{
MatXdC m{ { 1., 2. } };
Eigen::Ref<const MatXdR> r(m); // r::m_object copy-initialized, and r points to it.
v.push_back(std::move(r)); // v[0]::m_object not initialized. v[0] points to r::m_object.
}
v[0](0, 0); // accesses destroyed memory. |
I wasn't aware that
I think |
As a work-around specifically for NAMESPACE_BEGIN(NB_NAMESPACE)
NAMESPACE_BEGIN(detail)
template<typename T>
struct type_caster<std::optional<Eigen::Ref<const T>>> : optional_caster<std::optional<Eigen::Ref<const T>>>
{
using Caster = typename type_caster::optional_caster::Caster;
using Map = typename Caster::Map;
using DMap = typename Caster::DMap;
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
if (src.is_none())
return true;
Caster caster;
if (!caster.from_python(src, flags_for_local_caster<T>(flags), cleanup) ||
!caster.template can_cast<T>())
return false;
if (caster.dcaster.caster.value.is_valid())
value.emplace(caster.dcaster.operator DMap());
else
value.emplace(caster.caster.operator Map());
return true;
}
};
NAMESPACE_END(detail)
NAMESPACE_END(NB_NAMESPACE) |
This sounds like a reasonable solution, I'll check it out. I could probably do the same with |
I am not in the position of accepting PRs here. The only way for nanobind itself to support what you need using the current release of Eigen seems to require your rather comprehensive changes, which @wjakob seems to at least dislike. So I've tried to provide a solution outside of it.
I guess the intent for its unusual copy semantics is to make it cheap to call functions that expect |
That's fine then. I'll just apply fixes within our own codebase.
You're right, since vectors can move it would be dangerous. I think I will suggest to my org we outright ban STL vectors (and I guess, sets, and maps?) for Eigen::Ref<const...> outside of optional.
This makes sense, I usually do
Instead, there's an open MR on Gitlab which seeks to remove the temporary |
What's the resolution? Shall I close the issue? |
We concluded that std::vector<Eigen::Ref> is in fact dangerous due to the temporary and weird copy semantics (even when there is a move ctor), and aside from my intrusive proposal for the type_caster there is no safe way of operating it (even in C++ itself). Thank you for the discussion, I'll be closing this issue and my PR! |
Problem description
Hello,
I would like to report a bug which happens under specific circumstances:
std::vector<Eigen::Ref<const MatrixXd>>
(with the default, fixed inner stride),Following @WKarel's work on PR #215, points 1-2 will lead to the creation of a temporary owned object in the
Ref::m_object
protected member. This happens on this line, becauseDMap
has dynamic strides, and theEigen::Ref
has fixed inner stride hence the temporary is created:nanobind/include/nanobind/eigen/dense.h
Line 467 in 8e5fd14
I know of two mitigations for now:
Eigen::Ref
nb::DRef
which was built to avoid copiesReproducible example code
The code prints the following result (and the asserts fail)
The text was updated successfully, but these errors were encountered: