Skip to content

Commit de72c33

Browse files
committed
Add the buffer interface for wrapped STL vectors
Allows use of vectors as python buffers, so for example they can be adopted without a copy by numpy.asarray Allows faster conversion of numeric buffers to vectors with memcpy instead of individually casting the elements
1 parent 2b92a49 commit de72c33

File tree

2 files changed

+41
-0
lines changed

2 files changed

+41
-0
lines changed

include/pybind11/stl_bind.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ struct is_comparable<T, enable_if_t<container_traits<T>::is_pair>> {
6363
template <typename, typename, typename... Args> void vector_if_copy_constructible(const Args&...) { }
6464
template <typename, typename, typename... Args> void vector_if_equal_operator(const Args&...) { }
6565
template <typename, typename, typename... Args> void vector_if_insertion_operator(const Args&...) { }
66+
template <typename, typename, typename, typename... Args> void vector_buffer(const Args&...) { }
6667

6768
template<typename Vector, typename Class_, enable_if_t<std::is_copy_constructible<typename Vector::value_type>::value, int> = 0>
6869
void vector_if_copy_constructible(Class_ &cl) {
@@ -126,6 +127,23 @@ template <typename Vector, typename Class_> auto vector_if_insertion_operator(Cl
126127
);
127128
}
128129

130+
// Provide the buffer interface for vectors if we have data(), the contained type is numeric and we have a format for it
131+
template <typename Class_, typename Vector, typename T>
132+
enable_if_t<std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, decltype(format_descriptor<T>::format(), std::declval<Vector>().data(), void())>
133+
vector_buffer(Class_& cl) {
134+
cl.def_buffer([](Vector& v) -> py::buffer_info {
135+
return py::buffer_info(v.data(), sizeof(T), py::format_descriptor<T>::format(), 1, {v.size()}, {sizeof(T)});
136+
});
137+
138+
cl.def("__init__", [](Vector& vec, py::buffer buf) {
139+
auto info = buf.request();
140+
if (info.ndim != 1 || info.strides[0] != info.itemsize || info.itemsize != sizeof(T) || info.format != py::format_descriptor<T>::format())
141+
throw pybind11::type_error();
142+
new (&vec) Vector(info.shape[0]);
143+
memcpy(vec.data(), info.ptr, info.shape[0] * sizeof(T));
144+
});
145+
}
146+
129147
NAMESPACE_END(detail)
130148

131149
//
@@ -152,6 +170,9 @@ pybind11::class_<Vector, holder_type> bind_vector(pybind11::module &m, std::stri
152170
// Register stream insertion operator (if possible)
153171
detail::vector_if_insertion_operator<Vector, Class_>(cl, name);
154172

173+
// Register the buffer interface (if possible)
174+
detail::vector_buffer<Class_, Vector, T>(cl);
175+
155176
cl.def("__init__", [](Vector &v, iterable it) {
156177
new (&v) Vector();
157178
try {

tests/test_stl_binders.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import pytest
2+
3+
with pytest.suppress(ImportError):
4+
import numpy as np
5+
16
def test_vector_int():
27
from pybind11_tests import VectorInt
38

@@ -25,6 +30,21 @@ def test_vector_int():
2530
del v_int2[0]
2631
assert v_int2 == VectorInt([0, 99, 2, 3])
2732

33+
@pytest.requires_numpy
34+
def test_vector_buffer():
35+
from pybind11_tests import VectorInt
36+
37+
a = np.array([1,2,3,4], dtype=np.int32)
38+
with pytest.raises(TypeError) as e_info:
39+
VectorInt(a)
40+
41+
a = np.array([1,2,3,4], dtype=np.uintc)
42+
v = VectorInt(a)
43+
assert v[2] == 3
44+
m = np.asarray(v)
45+
m[2] = 5
46+
assert v[2] == 5
47+
2848

2949
def test_vector_custom():
3050
from pybind11_tests import El, VectorEl, VectorVectorEl

0 commit comments

Comments
 (0)