Repository: stack-of-tasks/eigenpy (language: python)

Issue:
Proposal: port StdVectorPythonVisitor from Pinocchio's bindings
The header is here: https://github.com/stack-of-tasks/pinocchio/blob/devel/bindings/python/utils/std-vector.hpp (or a more recent version).
I recently suggested moving/copying the **`StdVectorPythonVisitor`** template from Pinocchio's bindings upstream to this repo, so that downstream packages don't have to import or copy the header from Pinocchio.

We could also import the **`StdContainerFromPythonList`** template.

What do we think of this ? I could do it if we choose to go ahead with it.


Discussion / hints:

Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1150ffc35..137aada35 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -5,7 +5,7 @@ repos:
     rev: v15.0.4
     hooks:
     -   id: clang-format
-        args: [--style=Google]
+        args: ['--style={BasedOnStyle: Google, SortIncludes: false}']
 -   repo: https://github.com/pre-commit/pre-commit-hooks
     rev: v4.4.0
     hooks:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cc4b50abf..da119a1ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -135,6 +135,7 @@ set(${PROJECT_NAME}_HEADERS
     include/eigenpy/exception.hpp
     include/eigenpy/scalar-conversion.hpp
     include/eigenpy/expose.hpp
+    include/eigenpy/copyable.hpp
     include/eigenpy/details.hpp
     include/eigenpy/fwd.hpp
     include/eigenpy/eigen-allocator.hpp
@@ -154,6 +155,9 @@ set(${PROJECT_NAME}_HEADERS
     include/eigenpy/user-type.hpp
     include/eigenpy/ufunc.hpp
     include/eigenpy/register.hpp
+    include/eigenpy/std-map.hpp
+    include/eigenpy/std-vector.hpp
+    include/eigenpy/pickle-vector.hpp
     include/eigenpy/stride.hpp
     include/eigenpy/swig.hpp
     include/eigenpy/version.hpp)
@@ -194,6 +198,7 @@ set(${PROJECT_NAME}_SOURCES
     src/angle-axis.cpp
     src/quaternion.cpp
     src/geometry-conversion.cpp
+    src/std-vector.cpp
     src/version.cpp)
 
 add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SOURCES}
diff --git a/cmake b/cmake
index c1439c989..277d1bd8a 160000
--- a/cmake
+++ b/cmake
@@ -1,1 +1,1 @@
-Subproject commit c1439c9894173c80e44173fd6af17232f20fa2dd
+Subproject commit 277d1bd8a5491e6235413bd716756249c3922232
diff --git a/include/eigenpy/copyable.hpp b/include/eigenpy/copyable.hpp
new file mode 100644
index 000000000..b6d8c5ed2
--- /dev/null
+++ b/include/eigenpy/copyable.hpp
@@ -0,0 +1,30 @@
+//
+// Copyright (c) 2016-2021 CNRS INRIA
+//
+
+#ifndef __eigenpy_utils_copyable_hpp__
+#define __eigenpy_utils_copyable_hpp__
+
+#include <boost/python.hpp>
+
+namespace eigenpy {
+
+namespace bp = boost::python;
+
+///
+/// \brief Add the Python method copy to allow a copy of this by calling the
+/// copy constructor.
+///
+template <class C>
+struct CopyableVisitor : public bp::def_visitor<CopyableVisitor<C> > {
+  template <class PyClass>
+  void visit(PyClass& cl) const {
+    cl.def("copy", &copy, bp::arg("self"), "Returns a copy of *this.");
+  }
+
+ private:
+  static C copy(const C& self) { return C(self); }
+};
+}  // namespace eigenpy
+
+#endif  // ifndef __eigenpy_utils_copyable_hpp__
diff --git a/include/eigenpy/eigen-allocator.hpp b/include/eigenpy/eigen-allocator.hpp
index 3cde4dd4d..2ca889dbe 100644
--- a/include/eigenpy/eigen-allocator.hpp
+++ b/include/eigenpy/eigen-allocator.hpp
@@ -232,6 +232,22 @@ struct EigenAllocator {
 };
 
 #if EIGEN_VERSION_AT_LEAST(3, 2, 0)
+/// @brief Check if we need to allocate @tparam MatType to convert @param
+/// pyArray.
+/// @details do not allocate if:
+/// want row-major & data C-contiguous OR
+/// want col-major & data F-contiguous OR
+/// you want a compile-time vector
+/// in these cases, data layout fits desired view layout
+template <typename MatType>
+inline bool is_arr_layout_compatible_with_mat_type(PyArrayObject *pyArray) {
+  bool is_array_C_cont = PyArray_IS_C_CONTIGUOUS(pyArray);
+  bool is_array_F_cont = PyArray_IS_F_CONTIGUOUS(pyArray);
+  return (MatType::IsRowMajor && is_array_C_cont) ||
+         (!MatType::IsRowMajor && is_array_F_cont) ||
+         MatType::IsVectorAtCompileTime;
+}
+
 template <typename MatType, int Options, typename Stride>
 struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > {
   typedef Eigen::Ref<MatType, Options, Stride> RefType;
@@ -255,16 +271,9 @@ struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > {
     const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray);
     const int Scalar_type_code = Register::getTypeCode<Scalar>();
     if (pyArray_type_code != Scalar_type_code) need_to_allocate |= true;
-    if ((MatType::IsRowMajor && (PyArray_IS_C_CONTIGUOUS(pyArray) &&
-                                 !PyArray_IS_F_CONTIGUOUS(pyArray))) ||
-        (!MatType::IsRowMajor && (PyArray_IS_F_CONTIGUOUS(pyArray) &&
-                                  !PyArray_IS_C_CONTIGUOUS(pyArray))) ||
-        MatType::IsVectorAtCompileTime ||
-        (PyArray_IS_F_CONTIGUOUS(pyArray) &&
-         PyArray_IS_C_CONTIGUOUS(pyArray)))  // no need to allocate
-      need_to_allocate |= false;
-    else
-      need_to_allocate |= true;
+    bool incompatible_layout =
+        !is_arr_layout_compatible_with_mat_type<MatType>(pyArray);
+    need_to_allocate |= incompatible_layout;
     if (Options !=
         Eigen::Unaligned)  // we need to check whether the memory is correctly
                            // aligned and composed of a continuous segment
@@ -365,16 +374,9 @@ struct EigenAllocator<const Eigen::Ref<const MatType, Options, Stride> > {
     const int Scalar_type_code = Register::getTypeCode<Scalar>();
 
     if (pyArray_type_code != Scalar_type_code) need_to_allocate |= true;
-    if ((MatType::IsRowMajor && (PyArray_IS_C_CONTIGUOUS(pyArray) &&
-                                 !PyArray_IS_F_CONTIGUOUS(pyArray))) ||
-        (!MatType::IsRowMajor && (PyArray_IS_F_CONTIGUOUS(pyArray) &&
-                                  !PyArray_IS_C_CONTIGUOUS(pyArray))) ||
-        MatType::IsVectorAtCompileTime ||
-        (PyArray_IS_F_CONTIGUOUS(pyArray) &&
-         PyArray_IS_C_CONTIGUOUS(pyArray)))  // no need to allocate
-      need_to_allocate |= false;
-    else
-      need_to_allocate |= true;
+    bool incompatible_layout =
+        !is_arr_layout_compatible_with_mat_type<MatType>(pyArray);
+    need_to_allocate |= incompatible_layout;
     if (Options !=
         Eigen::Unaligned)  // we need to check whether the memory is correctly
                            // aligned and composed of a continuous segment
diff --git a/include/eigenpy/pickle-vector.hpp b/include/eigenpy/pickle-vector.hpp
new file mode 100644
index 000000000..368bb30c3
--- /dev/null
+++ b/include/eigenpy/pickle-vector.hpp
@@ -0,0 +1,46 @@
+//
+// Copyright (c) 2019-2020 CNRS INRIA
+//
+
+#ifndef __eigenpy_utils_pickle_vector_hpp__
+#define __eigenpy_utils_pickle_vector_hpp__
+
+#include <boost/python.hpp>
+#include <boost/python/stl_iterator.hpp>
+#include <boost/python/tuple.hpp>
+
+namespace eigenpy {
+///
+/// \brief Create a pickle interface for the std::vector
+///
+/// \tparam VecType Vector Type to pickle
+///
+template <typename VecType>
+struct PickleVector : boost::python::pickle_suite {
+  static boost::python::tuple getinitargs(const VecType&) {
+    return boost::python::make_tuple();
+  }
+
+  static boost::python::tuple getstate(boost::python::object op) {
+    return boost::python::make_tuple(
+        boost::python::list(boost::python::extract<const VecType&>(op)()));
+  }
+
+  static void setstate(boost::python::object op, boost::python::tuple tup) {
+    if (boost::python::len(tup) > 0) {
+      VecType& o = boost::python::extract<VecType&>(op)();
+      boost::python::stl_input_iterator<typename VecType::value_type> begin(
+          tup[0]),
+          end;
+      while (begin != end) {
+        o.push_back(*begin);
+        ++begin;
+      }
+    }
+  }
+
+  static bool getstate_manages_dict() { return true; }
+};
+}  // namespace eigenpy
+
+#endif  // ifndef __eigenpy_utils_pickle_vector_hpp__
diff --git a/include/eigenpy/quaternion.hpp b/include/eigenpy/quaternion.hpp
index 61ee052a9..88f899db1 100644
--- a/include/eigenpy/quaternion.hpp
+++ b/include/eigenpy/quaternion.hpp
@@ -294,8 +294,8 @@ class QuaternionVisitor
     return q;
   }
 
-  static Quaternion* FromTwoVectors(const Eigen::Ref<Vector3> u,
-                                    const Eigen::Ref<Vector3> v) {
+  static Quaternion* FromTwoVectors(const Eigen::Ref<const Vector3> u,
+                                    const Eigen::Ref<const Vector3> v) {
     Quaternion* q(new Quaternion);
     q->setFromTwoVectors(u, v);
     return q;
@@ -308,12 +308,12 @@ class QuaternionVisitor
 
   static Quaternion* DefaultConstructor() { return new Quaternion; }
 
-  static Quaternion* FromOneVector(const Eigen::Ref<Vector4> v) {
+  static Quaternion* FromOneVector(const Eigen::Ref<const Vector4> v) {
     Quaternion* q(new Quaternion(v[3], v[0], v[1], v[2]));
     return q;
   }
 
-  static Quaternion* FromRotationMatrix(const Eigen::Ref<Matrix3> R) {
+  static Quaternion* FromRotationMatrix(const Eigen::Ref<const Matrix3> R) {
     Quaternion* q(new Quaternion(R));
     return q;
   }
diff --git a/include/eigenpy/std-map.hpp b/include/eigenpy/std-map.hpp
new file mode 100644
index 000000000..b6748f571
--- /dev/null
+++ b/include/eigenpy/std-map.hpp
@@ -0,0 +1,64 @@
+/// Copyright (c) 2016-2022 CNRS INRIA
+/// This file was taken from Pinocchio (header
+/// <pinocchio/bindings/python/utils/std-vector.hpp>)
+///
+
+#ifndef __eigenpy_utils_map_hpp__
+#define __eigenpy_utils_map_hpp__
+
+#include <boost/python/suite/indexing/map_indexing_suite.hpp>
+
+namespace eigenpy {
+namespace details {
+template <typename Container>
+struct overload_base_get_item_for_std_map
+    : public boost::python::def_visitor<
+          overload_base_get_item_for_std_map<Container> > {
+  typedef typename Container::value_type value_type;
+  typedef typename Container::value_type::second_type data_type;
+  typedef typename Container::key_type key_type;
+  typedef typename Container::key_type index_type;
+
+  template <class Class>
+  void visit(Class& cl) const {
+    cl.def("__getitem__", &base_get_item);
+  }
+
+ private:
+  static boost::python::object base_get_item(
+      boost::python::back_reference<Container&> container, PyObject* i_) {
+    namespace bp = ::boost::python;
+
+    index_type idx = convert_index(container.get(), i_);
+    typename Container::iterator i = container.get().find(idx);
+    if (i == container.get().end()) {
+      PyErr_SetString(PyExc_KeyError, "Invalid key");
+      bp::throw_error_already_set();
+    }
+
+    typename bp::to_python_indirect<data_type&,
+                                    bp::detail::make_reference_holder>
+        convert;
+    return bp::object(bp::handle<>(convert(i->second)));
+  }
+
+  static index_type convert_index(Container& /*container*/, PyObject* i_) {
+    namespace bp = ::boost::python;
+    bp::extract<key_type const&> i(i_);
+    if (i.check()) {
+      return i();
+    } else {
+      bp::extract<key_type> i(i_);
+      if (i.check()) return i();
+    }
+
+    PyErr_SetString(PyExc_TypeError, "Invalid index type");
+    bp::throw_error_already_set();
+    return index_type();
+  }
+};
+
+}  // namespace details
+}  // namespace eigenpy
+
+#endif  // ifndef __eigenpy_utils_map_hpp__
diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp
new file mode 100644
index 000000000..cb6d869e0
--- /dev/null
+++ b/include/eigenpy/std-vector.hpp
@@ -0,0 +1,458 @@
+/// Copyright (c) 2016-2022 CNRS INRIA
+/// This file was taken from Pinocchio (header
+/// <pinocchio/bindings/python/utils/std-vector.hpp>)
+///
+
+#ifndef __eigenpy_utils_std_vector_hpp__
+#define __eigenpy_utils_std_vector_hpp__
+
+#include <boost/mpl/if.hpp>
+#include <boost/python.hpp>
+#include <boost/python/stl_iterator.hpp>
+#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+#include <iterator>
+#include <string>
+#include <vector>
+
+#include "eigenpy/config.hpp"
+#include "eigenpy/copyable.hpp"
+#include "eigenpy/eigen-to-python.hpp"
+#include "eigenpy/pickle-vector.hpp"
+#include "eigenpy/registration.hpp"
+
+namespace eigenpy {
+// Forward declaration
+template <typename vector_type, bool NoProxy = false>
+struct StdContainerFromPythonList;
+
+namespace details {
+
+/// \brief Check if a PyObject can be converted to an std::vector<T>.
+template <typename T>
+bool from_python_list(PyObject *obj_ptr, T *) {
+  namespace bp = ::boost::python;
+
+  // Check if it is a list
+  if (!PyList_Check(obj_ptr)) return false;
+
+  // Retrieve the underlying list
+  bp::object bp_obj(bp::handle<>(bp::borrowed(obj_ptr)));
+  bp::list bp_list(bp_obj);
+  bp::ssize_t list_size = bp::len(bp_list);
+
+  // Check if all the elements contained in the current vector is of type T
+  for (bp::ssize_t k = 0; k < list_size; ++k) {
+    bp::extract<T> elt(bp_list[k]);
+    if (!elt.check()) return false;
+  }
+
+  return true;
+}
+
+template <typename vector_type, bool NoProxy>
+struct build_list {
+  static ::boost::python::list run(vector_type &vec) {
+    namespace bp = ::boost::python;
+
+    bp::list bp_list;
+    for (size_t k = 0; k < vec.size(); ++k) {
+      bp_list.append(boost::ref(vec[k]));
+    }
+    return bp_list;
+  }
+};
+
+template <typename vector_type>
+struct build_list<vector_type, true> {
+  static ::boost::python::list run(vector_type &vec) {
+    namespace bp = ::boost::python;
+
+    typedef bp::iterator<vector_type> iterator;
+    return bp::list(iterator()(vec));
+  }
+};
+
+/// \brief Change the behaviour of indexing (method __getitem__ in Python).
+/// This is suitable for container of Eigen matrix objects if you want to mutate
+/// them.
+template <typename Container>
+struct overload_base_get_item_for_std_vector
+    : public boost::python::def_visitor<
+          overload_base_get_item_for_std_vector<Container> > {
+  typedef typename Container::value_type value_type;
+  typedef typename Container::value_type data_type;
+  typedef size_t index_type;
+
+  template <class Class>
+  void visit(Class &cl) const {
+    cl.def("__getitem__", &base_get_item);
+  }
+
+ private:
+  static boost::python::object base_get_item(
+      boost::python::back_reference<Container &> container, PyObject *i_) {
+    namespace bp = ::boost::python;
+
+    index_type idx = convert_index(container.get(), i_);
+    typename Container::iterator i = container.get().begin();
+    std::advance(i, idx);
+    if (i == container.get().end()) {
+      PyErr_SetString(PyExc_KeyError, "Invalid index");
+      bp::throw_error_already_set();
+    }
+
+    typename bp::to_python_indirect<data_type &,
+                                    bp::detail::make_reference_holder>
+        convert;
+    return bp::object(bp::handle<>(convert(*i)));
+  }
+
+  static index_type convert_index(Container &container, PyObject *i_) {
+    namespace bp = boost::python;
+    bp::extract<long> i(i_);
+    if (i.check()) {
+      long index = i();
+      if (index < 0) index += (long)container.size();
+      if (index >= long(container.size()) || index < 0) {
+        PyErr_SetString(PyExc_IndexError, "Index out of range");
+        bp::throw_error_already_set();
+      }
+      return (index_type)index;
+    }
+
+    PyErr_SetString(PyExc_TypeError, "Invalid index type");
+    bp::throw_error_already_set();
+    return index_type();
+  }
+};
+}  // namespace details
+}  // namespace eigenpy
+
+namespace boost {
+namespace python {
+
+template <typename MatrixType>
+struct extract_to_eigen_ref
+    : converter::extract_rvalue<Eigen::Ref<MatrixType> > {
+  typedef Eigen::Ref<MatrixType> RefType;
+
+ protected:
+  typedef converter::extract_rvalue<RefType> base;
+
+ public:
+  typedef RefType result_type;
+
+  operator result_type() const { return (*this)(); }
+
+  extract_to_eigen_ref(PyObject *o) : base(o) {}
+  extract_to_eigen_ref(api::object const &o) : base(o.ptr()) {}
+};
+
+/// \brief Specialization of the boost::python::extract struct for references to
+/// Eigen matrix objects.
+template <typename Scalar, int Rows, int Cols, int Options, int MaxRows,
+          int MaxCols>
+struct extract<Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> &>
+    : extract_to_eigen_ref<
+          Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> > {
+  typedef Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols>
+      MatrixType;
+  typedef extract_to_eigen_ref<MatrixType> base;
+  extract(PyObject *o) : base(o) {}
+  extract(api::object const &o) : base(o.ptr()) {}
+};
+
+template <typename Derived>
+struct extract<Eigen::MatrixBase<Derived> &>
+    : extract_to_eigen_ref<Eigen::MatrixBase<Derived> > {
+  typedef Eigen::MatrixBase<Derived> MatrixType;
+  typedef extract_to_eigen_ref<MatrixType> base;
+  extract(PyObject *o) : base(o) {}
+  extract(api::object const &o) : base(o.ptr()) {}
+};
+
+template <typename Derived>
+struct extract<Eigen::RefBase<Derived> &>
+    : extract_to_eigen_ref<Eigen::RefBase<Derived> > {
+  typedef Eigen::RefBase<Derived> MatrixType;
+  typedef extract_to_eigen_ref<MatrixType> base;
+  extract(PyObject *o) : base(o) {}
+  extract(api::object const &o) : base(o.ptr()) {}
+};
+
+namespace converter {
+
+template <typename Type, class Allocator>
+struct reference_arg_from_python<std::vector<Type, Allocator> &>
+    : arg_lvalue_from_python_base {
+  typedef std::vector<Type, Allocator> vector_type;
+  typedef vector_type &ref_vector_type;
+  typedef ref_vector_type result_type;
+  typedef extract<Type &> extract_type;
+
+  reference_arg_from_python(PyObject *py_obj)
+      : arg_lvalue_from_python_base(converter::get_lvalue_from_python(
+            py_obj, registered<vector_type>::converters)),
+        m_data(NULL),
+        m_source(py_obj),
+        vec_ptr(NULL) {
+    if (result() != 0)  // we have found a lvalue converter
+      return;
+
+    // Check if py_obj is a py_list, which can then be converted to an
+    // std::vector
+    bool is_convertible =
+        ::eigenpy::details::from_python_list(py_obj, (Type *)(0));
+    if (!is_convertible) return;
+
+    typedef ::eigenpy::StdContainerFromPythonList<vector_type> Constructor;
+    Constructor::construct(py_obj, &m_data.stage1);
+
+    void *&m_result = const_cast<void *&>(result());
+    m_result = m_data.stage1.convertible;
+    vec_ptr = reinterpret_cast<vector_type *>(m_data.storage.bytes);
+  }
+
+  result_type operator()() const {
+    return ::boost::python::detail::void_ptr_to_reference(result(),
+                                                          (result_type(*)())0);
+  }
+
+  ~reference_arg_from_python() {
+    if (m_data.stage1.convertible == m_data.storage.bytes) {
+      // Copy back the reference
+      const vector_type &vec = *vec_ptr;
+      list bp_list(handle<>(borrowed(m_source)));
+      for (size_t i = 0; i < vec.size(); ++i) {
+        typename extract_type::result_type elt = extract_type(bp_list[i]);
+        elt = vec[i];
+      }
+    }
+  }
+
+ private:
+  rvalue_from_python_data<ref_vector_type> m_data;
+  PyObject *m_source;
+  vector_type *vec_ptr;
+};
+
+}  // namespace converter
+}  // namespace python
+}  // namespace boost
+
+namespace eigenpy {
+
+///
+/// \brief Register the conversion from a Python list to a std::vector
+///
+/// \tparam vector_type A std container (e.g. std::vector or std::list)
+///
+template <typename vector_type, bool NoProxy>
+struct StdContainerFromPythonList {
+  typedef typename vector_type::value_type T;
+  typedef typename vector_type::allocator_type Allocator;
+
+  /// \brief Check if obj_ptr can be converted
+  static void *convertible(PyObject *obj_ptr) {
+    namespace bp = boost::python;
+
+    // Check if it is a list
+    if (!PyList_Check(obj_ptr)) return 0;
+
+    // Retrieve the underlying list
+    bp::object bp_obj(bp::handle<>(bp::borrowed(obj_ptr)));
+    bp::list bp_list(bp_obj);
+    bp::ssize_t list_size = bp::len(bp_list);
+
+    // Check if all the elements contained in the current vector is of type T
+    for (bp::ssize_t k = 0; k < list_size; ++k) {
+      bp::extract<T> elt(bp_list[k]);
+      if (!elt.check()) return 0;
+    }
+
+    return obj_ptr;
+  }
+
+  /// \brief Allocate the std::vector and fill it with the element contained in
+  /// the list
+  static void construct(
+      PyObject *obj_ptr,
+      boost::python::converter::rvalue_from_python_stage1_data *memory) {
+    namespace bp = boost::python;
+
+    // Extract the list
+    bp::object bp_obj(bp::handle<>(bp::borrowed(obj_ptr)));
+    bp::list bp_list(bp_obj);
+
+    void *storage =
+        reinterpret_cast<
+            bp::converter::rvalue_from_python_storage<vector_type> *>(
+            reinterpret_cast<void *>(memory))
+            ->storage.bytes;
+
+    typedef bp::stl_input_iterator<T> iterator;
+
+    // Build the std::vector
+    new (storage) vector_type(iterator(bp_list), iterator());
+
+    // Validate the construction
+    memory->convertible = storage;
+  }
+
+  static void register_converter() {
+    ::boost::python::converter::registry::push_back(
+        &convertible, &construct, ::boost::python::type_id<vector_type>());
+  }
+
+  static ::boost::python::list tolist(vector_type &self) {
+    return details::build_list<vector_type, NoProxy>::run(self);
+  }
+};
+
+namespace internal {
+
+template <typename T>
+struct has_operator_equal
+    : boost::mpl::if_<typename boost::is_base_of<Eigen::EigenBase<T>, T>::type,
+                      has_operator_equal<Eigen::EigenBase<T> >,
+                      boost::true_type>::type {};
+
+template <typename T, class A>
+struct has_operator_equal<std::vector<T, A> > : has_operator_equal<T> {};
+
+template <>
+struct has_operator_equal<bool> : boost::true_type {};
+
+template <typename EigenObject>
+struct has_operator_equal<Eigen::EigenBase<EigenObject> >
+    : has_operator_equal<typename EigenObject::Scalar> {};
+
+template <typename T, bool has_operator_equal_value = boost::is_base_of<
+                          boost::true_type, has_operator_equal<T> >::value>
+struct contains_algo;
+
+template <typename T>
+struct contains_algo<T, true> {
+  template <class Container, typename key_type>
+  static bool run(Container &container, key_type const &key) {
+    return std::find(container.begin(), container.end(), key) !=
+           container.end();
+  }
+};
+
+template <typename T>
+struct contains_algo<T, false> {
+  template <class Container, typename key_type>
+  static bool run(Container &container, key_type const &key) {
+    for (size_t k = 0; k < container.size(); ++k) {
+      if (&container[k] == &key) return true;
+    }
+    return false;
+  }
+};
+
+template <class Container, bool NoProxy>
+struct contains_vector_derived_policies
+    : public ::boost::python::vector_indexing_suite<
+          Container, NoProxy,
+          contains_vector_derived_policies<Container, NoProxy> > {
+  typedef typename Container::value_type key_type;
+
+  static bool contains(Container &container, key_type const &key) {
+    return contains_algo<key_type>::run(container, key);
+  }
+};
+}  // namespace internal
+
+struct EmptyPythonVisitor
+    : public ::boost::python::def_visitor<EmptyPythonVisitor> {
+  template <class classT>
+  void visit(classT &) const {}
+};
+
+///
+/// \brief Expose an std::vector from a type given as template argument.
+///
+/// \tparam T Type to expose as std::vector<T>.
+/// \tparam Allocator Type for the Allocator in std::vector<T,Allocator>.
+/// \tparam NoProxy When set to false, the elements will be copied when returned
+/// to Python. \tparam EnableFromPythonListConverter Enables the conversion from
+/// a Python list to a std::vector<T,Allocator>
+///
+/// \sa StdAlignedVectorPythonVisitor
+///
+template <class vector_type, bool NoProxy = false,
+          bool EnableFromPythonListConverter = true>
+struct StdVectorPythonVisitor
+    : public ::boost::python::vector_indexing_suite<
+          vector_type, NoProxy,
+          internal::contains_vector_derived_policies<vector_type, NoProxy> >,
+      public StdContainerFromPythonList<vector_type, NoProxy> {
+  typedef typename vector_type::value_type value_type;
+  typedef typename vector_type::allocator_type allocator_type;
+  typedef StdContainerFromPythonList<vector_type, NoProxy>
+      FromPythonListConverter;
+
+  static void expose(const std::string &class_name,
+                     const std::string &doc_string = "") {
+    expose(class_name, doc_string, EmptyPythonVisitor());
+  }
+
+  template <typename VisitorDerived>
+  static void expose(
+      const std::string &class_name,
+      const boost::python::def_visitor<VisitorDerived> &visitor) {
+    expose(class_name, "", visitor);
+  }
+
+  template <typename VisitorDerived>
+  static void expose(
+      const std::string &class_name, const std::string &doc_string,
+      const boost::python::def_visitor<VisitorDerived> &visitor) {
+    namespace bp = boost::python;
+
+    if (!register_symbolic_link_to_registered_type<vector_type>()) {
+      bp::class_<vector_type> cl(class_name.c_str(), doc_string.c_str());
+      cl.def(StdVectorPythonVisitor())
+
+          .def(bp::init<size_t, const value_type &>(
+              bp::args("self", "size", "value"),
+              "Constructor from a given size and a given value."))
+          .def(bp::init<const vector_type &>(bp::args("self", "other"),
+                                             "Copy constructor"))
+
+          .def("tolist", &FromPythonListConverter::tolist, bp::arg("self"),
+               "Returns the std::vector as a Python list.")
+          .def(visitor)
+          .def("reserve", &vector_type::reserve,
+               (bp::arg("self"), bp::arg("new_cap")),
+               "Increase the capacity of the vector to a value that's greater "
+               "or equal to new_cap.")
+          .def_pickle(PickleVector<vector_type>())
+          .def(CopyableVisitor<vector_type>());
+
+      // Register conversion
+      if (EnableFromPythonListConverter)
+        FromPythonListConverter::register_converter();
+    }
+  }
+};
+
+/**
+ * Expose std::vector for given matrix or vector sizes.
+ */
+void EIGENPY_DLLAPI exposeStdVector();
+
+template <typename MatType>
+void exposeStdVectorEigenSpecificType(const char *name) {
+  typedef std::vector<MatType> VecMatType;
+  std::string full_name = "StdVec_";
+  full_name += name;
+  StdVectorPythonVisitor<VecMatType, false>::expose(
+      full_name.c_str(),
+      details::overload_base_get_item_for_std_vector<VecMatType>());
+}
+
+}  // namespace eigenpy
+
+#endif  // ifndef __eigenpy_utils_std_vector_hpp__
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index 10f7d755e..5de5574b2 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -35,6 +35,12 @@ set_target_properties(
   PROPERTIES PREFIX ""
              SUFFIX ${PYTHON_EXT_SUFFIX}
              LIBRARY_OUTPUT_DIRECTORY
+             "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
+             LIBRARY_OUTPUT_DIRECTORY_<CONFIG>
+             "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
+             RUNTIME_OUTPUT_DIRECTORY
+             "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
+             RUNTIME_OUTPUT_DIRECTORY_<CONFIG>
              "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}")
 
 if(UNIX AND NOT APPLE)
diff --git a/python/main.cpp b/python/main.cpp
index b36c56e06..9c5b49a82 100644
--- a/python/main.cpp
+++ b/python/main.cpp
@@ -11,6 +11,7 @@
 #include "eigenpy/geometry.hpp"
 #include "eigenpy/solvers/preconditioners.hpp"
 #include "eigenpy/solvers/solvers.hpp"
+#include "eigenpy/std-vector.hpp"
 #include "eigenpy/utils/is-approx.hpp"
 #include "eigenpy/version.hpp"
 
@@ -30,6 +31,7 @@ BOOST_PYTHON_MODULE(eigenpy_pywrap) {
   exposeAngleAxis();
   exposeQuaternion();
   exposeGeometryConversion();
+  exposeStdVector();
 
   exposeComputationInfo();
 
diff --git a/src/std-vector.cpp b/src/std-vector.cpp
new file mode 100644
index 000000000..45cbcd315
--- /dev/null
+++ b/src/std-vector.cpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2022, CNRS
+ * Copyright 2022, INRIA
+ */
+
+#include "eigenpy/std-vector.hpp"
+
+namespace eigenpy {
+
+void exposeStdVector() {
+  exposeStdVectorEigenSpecificType<Eigen::MatrixXd>("MatrixXd");
+  exposeStdVectorEigenSpecificType<Eigen::VectorXd>("VectorXd");
+
+  exposeStdVectorEigenSpecificType<Eigen::MatrixXi>("MatrixXi");
+  exposeStdVectorEigenSpecificType<Eigen::VectorXi>("VectorXi");
+}
+
+}  // namespace eigenpy


Test patch:
diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
index 55d6b5699..7ef9001d4 100644
--- a/unittest/CMakeLists.txt
+++ b/unittest/CMakeLists.txt
@@ -33,6 +33,7 @@ if(NOT ${EIGEN3_VERSION} VERSION_LESS "3.2.0")
   add_lib_unit_test(eigen_ref)
 endif()
 add_lib_unit_test(user_type)
+add_lib_unit_test(std_vector)
 
 add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest")
 add_python_unit_test("py-geometry" "unittest/python/test_geometry.py"
@@ -78,3 +79,7 @@ if(NOT WIN32)
                        "python;unittest")
   set_tests_properties("py-MINRES" PROPERTIES DEPENDS ${PYWRAP})
 endif(NOT WIN32)
+
+add_python_unit_test("py-std-vector" "unittest/python/test_std_vector.py"
+                     "python;unittest")
+set_tests_properties("py-std-vector" PROPERTIES DEPENDS ${PYWRAP})
diff --git a/unittest/eigen_ref.cpp b/unittest/eigen_ref.cpp
index 78896b9a3..9c334d10b 100644
--- a/unittest/eigen_ref.cpp
+++ b/unittest/eigen_ref.cpp
@@ -6,7 +6,6 @@
 #include <iostream>
 
 #include "eigenpy/eigenpy.hpp"
-// include main header first
 #include "eigenpy/eigen-from-python.hpp"
 
 using namespace Eigen;
@@ -15,7 +14,7 @@ using namespace eigenpy;
 template <typename MatType>
 void printMatrix(const Eigen::Ref<const MatType> mat) {
   if (MatType::IsVectorAtCompileTime) std::cout << "isVector" << std::endl;
-  std::cout << "size: cols " << mat.cols() << " rows " << mat.rows()
+  std::cout << "input size: cols " << mat.cols() << " rows " << mat.rows()
             << std::endl;
   std::cout << mat << std::endl;
 }
@@ -58,20 +57,22 @@ void fill(Eigen::Ref<MatType> mat, const typename MatType::Scalar& value) {
   mat.fill(value);
 }
 
+/// Get ref to a static matrix of size ( @p rows, @p cols )
 template <typename MatType>
-Eigen::Ref<MatType> asRef(const int rows, const int cols) {
+Eigen::Ref<MatType> getRefToStatic(const int rows, const int cols) {
   static MatType mat(rows, cols);
-  std::cout << "mat:\n" << mat << std::endl;
+  std::cout << "create ref to matrix of size (" << rows << "," << cols << ")\n";
   return mat;
 }
 
 template <typename MatType>
 Eigen::Ref<MatType> asRef(Eigen::Ref<MatType> mat) {
+  std::cout << "create Ref to input mutable Ref\n";
   return Eigen::Ref<MatType>(mat);
 }
 
 template <typename MatType>
-const Eigen::Ref<const MatType> asConstRef(Eigen::Ref<MatType> mat) {
+const Eigen::Ref<const MatType> asConstRef(Eigen::Ref<const MatType> mat) {
   return Eigen::Ref<const MatType>(mat);
 }
 
@@ -82,8 +83,8 @@ struct modify_block {
   virtual void call(Eigen::Ref<MatrixXd> mat) = 0;
 };
 
-struct modify_wrap : modify_block, bp::wrapper<modify_block> {
-  modify_wrap() : modify_block() {}
+struct modify_block_wrap : modify_block, bp::wrapper<modify_block> {
+  modify_block_wrap() : modify_block() {}
   void call(Eigen::Ref<MatrixXd> mat) { this->get_override("call")(mat); }
 };
 
@@ -112,20 +113,18 @@ BOOST_PYTHON_MODULE(eigen_ref) {
   bp::def("fillVec", fill<VectorXd>);
   bp::def("fill", fill<MatrixXd>);
 
-  bp::def("asRef",
-          (Eigen::Ref<MatrixXd>(*)(const int, const int))asRef<MatrixXd>);
-  bp::def("asRef",
-          (Eigen::Ref<MatrixXd>(*)(Eigen::Ref<MatrixXd>))asRef<MatrixXd>);
-  bp::def("asConstRef", (const Eigen::Ref<const MatrixXd> (*)(
-                            Eigen::Ref<MatrixXd>))asConstRef<MatrixXd>);
+  bp::def("getRefToStatic", getRefToStatic<MatrixXd>);
+  bp::def("asRef", asRef<MatrixXd>);
+  bp::def("asConstRef", asConstRef<MatrixXd>);
 
   bp::def("getBlock", &getBlock<MatrixXd>);
   bp::def("editBlock", &editBlock<MatrixXd>);
 
-  bp::class_<modify_wrap, boost::noncopyable>("modify_block", bp::init<>())
+  bp::class_<modify_block_wrap, boost::noncopyable>("modify_block",
+                                                    bp::init<>())
       .def_readonly("J", &modify_block::J)
       .def("modify", &modify_block::modify)
-      .def("call", bp::pure_virtual(&modify_wrap::call));
+      .def("call", bp::pure_virtual(&modify_block_wrap::call));
 
   bp::class_<has_ref_member, boost::noncopyable>("has_ref_member", bp::init<>())
       .def_readonly("J", &has_ref_member::J)
diff --git a/unittest/python/test_eigen_ref.py b/unittest/python/test_eigen_ref.py
index 080264362..fe43e501f 100644
--- a/unittest/python/test_eigen_ref.py
+++ b/unittest/python/test_eigen_ref.py
@@ -1,6 +1,7 @@
 import numpy as np
 from eigen_ref import (
     printMatrix,
+    getRefToStatic,
     asRef,
     asConstRef,
     fill,
@@ -11,61 +12,79 @@
 )
 
 
-def test(mat):
+def test_fill_print(mat):
 
+    print("print matrix:")
     printMatrix(mat)
+    print("calling fill():")
     fill(mat, 1.0)
-    printMatrix(mat)
     assert np.array_equal(mat, np.full(mat.shape, 1.0))
 
-    A_ref = asRef(mat.shape[0], mat.shape[1])
+    print("fill a slice")
+    mat[:, :] = 0.0
+    fill(mat[:3, :2], 1.0)
+    printMatrix(mat[:3, :2])
+    assert np.array_equal(mat[:3, :2], np.ones((3, 2)))
+
+
+def test_create_ref_to_static(mat):
+    # create ref to static:
+    print()
+    print("[asRef(int, int)]")
+    A_ref = getRefToStatic(mat.shape[0], mat.shape[1])
     A_ref.fill(1.0)
-    A_ref2 = asRef(mat.shape[0], mat.shape[1])
+    A_ref[0, 1] = -1.0
+    print("make second reference:")
+    A_ref2 = getRefToStatic(mat.shape[0], mat.shape[1])
+    print(A_ref2)
 
     assert np.array_equal(A_ref, A_ref2)
 
     A_ref2.fill(0)
     assert np.array_equal(A_ref, A_ref2)
 
-    ref = asRef(mat)
-    assert np.all(ref == mat)
-
-    const_ref = asConstRef(mat)
-    assert np.all(const_ref == mat)
 
-    mat.fill(0.0)
-    fill(mat[:3, :2], 1.0)
+def test_create_ref(mat):
+    print("[asRef(mat)]")
+    ref = asRef(mat)
+    assert np.array_equal(ref, mat), "ref=\n{}\nmat=\n{}".format(ref, mat)
+    assert not (ref.flags.owndata)
+    assert ref.flags.writeable
 
-    assert np.all(mat[:3, :2] == np.ones((3, 2)))
 
-    mat.fill(0.0)
-    fill(mat[:2, :3], 1.0)
+def test_create_const_ref(mat):
+    print("[asConstRef]")
+    const_ref = asConstRef(mat)
+    assert np.array_equal(const_ref, mat), "ref=\n{}\nmat=\n{}".format(const_ref, mat)
+    assert not (const_ref.flags.writeable)
+    assert not (const_ref.flags.owndata)
 
-    assert np.all(mat[:2, :3] == np.ones((2, 3)))
 
+def test_edit_block(rows, cols):
+    print("set mat data to arange()")
     mat.fill(0.0)
     mat[:, :] = np.arange(rows * cols).reshape(rows, cols)
-    printMatrix(mat)
     mat0 = mat.copy()
-    mat_as_C_order = np.array(mat, order="F")
     for i, rowsize, colsize in ([0, 3, 2], [1, 1, 2], [0, 3, 1]):
         print("taking block [{}:{}, {}:{}]".format(i, rowsize + i, 0, colsize))
-        B = getBlock(mat_as_C_order, i, 0, rowsize, colsize)
-        lhs = mat_as_C_order[i : rowsize + i, :colsize]
-        print("should be:\n{}\ngot:\n{}".format(lhs, B))
-        assert np.array_equal(lhs, B.reshape(rowsize, colsize))
+        B = getBlock(mat, i, 0, rowsize, colsize)
+        B = B.reshape(rowsize, colsize)
+        lhs = mat[i : rowsize + i, :colsize]
+        assert np.array_equal(lhs, B), "got lhs\n{}\nrhs B=\n{}".format(lhs, B)
 
         B[:] = 1.0
         rhs = np.ones((rowsize, colsize))
-        assert np.array_equal(mat_as_C_order[i : rowsize + i, :colsize], rhs)
+        assert np.array_equal(mat[i : rowsize + i, :colsize], rhs)
 
-        mat_as_C_order[:, :] = mat0
+        mat[:, :] = mat0
 
-    mat_copy = mat_as_C_order.copy()
-    editBlock(mat_as_C_order, 0, 0, 3, 2)
+    mat.fill(0.0)
+    mat_copy = mat.copy()
+    print("[editBlock]")
+    editBlock(mat, 0, 0, 3, 2)
     mat_copy[:3, :2] = np.arange(6).reshape(3, 2)
 
-    assert np.array_equal(mat_as_C_order, mat_copy)
+    assert np.array_equal(mat, mat_copy)
 
     class ModifyBlockImpl(modify_block):
         def __init__(self):
@@ -91,9 +110,26 @@ def call(self, mat):
     assert np.array_equal(hasref.J, J_true)
 
 
-rows = 10
-cols = 30
+def do_test(mat):
+    test_fill_print(mat)
+    test_create_ref_to_static(mat)
+    test_create_const_ref(mat)
+    test_create_ref(mat)
+    test_edit_block(rows, cols)
+    print("=" * 10)
+
+
+if __name__ == "__main__":
+    rows = 8
+    cols = 10
 
-mat = np.ones((rows, cols), order="F")
+    mat = np.ones((rows, cols), order="F")
+    mat[0, 0] = 0
+    mat[1:5, 1:5] = 6
+    do_test(mat)
 
-test(mat)
+    # mat2 = np.ones((rows, cols))
+    # mat2[:2, :5] = 0.
+    # mat2[2:4, 1:4] = 2
+    # mat2[:, -1] = 3
+    # do_test(mat2)
diff --git a/unittest/python/test_std_vector.py b/unittest/python/test_std_vector.py
new file mode 100644
index 000000000..52fd4c0d3
--- /dev/null
+++ b/unittest/python/test_std_vector.py
@@ -0,0 +1,85 @@
+import numpy as np
+import eigenpy
+import inspect
+import pprint
+import std_vector
+from std_vector import printVectorOfMatrix, printVectorOf3x3, copyStdVector
+
+np.random.seed(0)
+
+l1 = [np.random.randn(3), np.random.randn(2)]
+l2 = eigenpy.StdVec_VectorXd(l1)
+l3 = [np.random.randn(2, 2), np.random.randn(3, 1)]
+l3.append(np.asfortranarray(np.eye(2)))
+l3.append(np.eye(2))
+l4 = [np.random.randn(3, 3).T for _ in range(3)]
+l4[-1] = l4[-1].T
+
+
+def checkAllValues(li1, li2):
+    assert len(li1) == len(li2)
+    n = len(li1)
+    for i in range(n):
+        assert np.array_equal(li1[i], li2[i])
+
+
+checkAllValues(l1, l2)
+checkAllValues(l1, copyStdVector(l1))
+
+l2[0][:2] = 0.0
+assert np.allclose(l2[0][:2], 0.0)
+
+print("l1")
+printVectorOfMatrix(l1)
+print("l2")
+printVectorOfMatrix(l2)
+print("l3")
+printVectorOfMatrix(l3)
+
+
+l4_copy = copyStdVector(l4)
+assert isinstance(l4_copy, eigenpy.StdVec_MatrixXd)
+
+assert "StdVec_Mat3d" in printVectorOf3x3.__doc__
+printVectorOf3x3(l4)
+
+l4_copy2 = std_vector.copyStdVec_3x3(l4)
+assert isinstance(l4_copy2, std_vector.StdVec_Mat3d)
+
+
+def checkZero(l):
+    for x in l:
+        assert np.allclose(x, 0.0), "x = {}".format(x)
+
+
+print("Check setZero() works:")
+print("l1:")
+std_vector.setZero(l1)
+print(l1)
+checkZero(l1)
+print("-----------------")
+
+print("l2:")
+l2_py = l2.tolist()
+std_vector.setZero(l2_py)
+pprint.pprint(l2_py)
+checkZero(l2_py)
+print("-----------------")
+
+l3_copy = copyStdVector(l3)
+print("l3_std:")
+std_vector.setZero(l3_copy)
+pprint.pprint(list(l3_copy))
+checkZero(l3_copy)
+print("-----------------")
+
+# print("l3_python:")
+# vector.setZero(l3)
+# pprint.pprint(list(l3))
+# checkZero(l3)
+# print("-----------------")
+
+# print("l4:")
+# vector.setZero(l4)
+# pprint.pprint(list(l4))
+# checkZero(l4)
diff --git a/unittest/std_vector.cpp b/unittest/std_vector.cpp
new file mode 100644
index 000000000..a3679917a
--- /dev/null
+++ b/unittest/std_vector.cpp
@@ -0,0 +1,50 @@
+/// @file
+/// @copyright Copyright 2022, CNRS
+/// @copyright Copyright 2022, INRIA
+#include <ostream>
+
+#include "eigenpy/eigenpy.hpp"
+#include "eigenpy/eigen-from-python.hpp"
+#include "eigenpy/std-vector.hpp"
+
+template <typename MatType>
+void printVectorOfMatrix(const std::vector<MatType> &Ms) {
+  const std::size_t n = Ms.size();
+  for (std::size_t i = 0; i < n; i++) {
+    std::cout << "el[" << i << "] =\n" << Ms[i] << '\n';
+  }
+}
+
+template <typename MatType>
+std::vector<MatType> copy(const std::vector<MatType> &Ms) {
+  std::vector<MatType> out = Ms;
+  return out;
+}
+
+template <typename MatType>
+void setZero(std::vector<MatType> &Ms) {
+  for (std::size_t i = 0; i < Ms.size(); i++) {
+    Ms[i].setZero();
+  }
+}
+
+BOOST_PYTHON_MODULE(std_vector) {
+  namespace bp = boost::python;
+  using namespace eigenpy;
+
+  enableEigenPy();
+
+  bp::def("printVectorOfMatrix", printVectorOfMatrix<Eigen::VectorXd>);
+  bp::def("printVectorOfMatrix", printVectorOfMatrix<Eigen::MatrixXd>);
+
+  bp::def("copyStdVector", copy<Eigen::MatrixXd>);
+  bp::def("copyStdVector", copy<Eigen::VectorXd>);
+
+  exposeStdVectorEigenSpecificType<Eigen::Matrix3d>("Mat3d");
+  bp::def("printVectorOf3x3", printVectorOfMatrix<Eigen::Matrix3d>);
+  bp::def("copyStdVec_3x3", copy<Eigen::Matrix3d>, bp::args("mats"));
+
+  typedef Eigen::Ref<Eigen::MatrixXd> RefXd;
+  StdVectorPythonVisitor<std::vector<RefXd>, true>::expose("StdVec_MatRef");
+  bp::def("setZero", setZero<Eigen::MatrixXd>, "Sets the coeffs to 0.");
+}