Bindings for std::span #969
Replies: 2 comments 7 replies
-
Nanobind is feature-scoped to C++17. It might make sense to develop some kind of C++20 (and beyond) extension layer, but I do not wish to have such functionality as part of this project. |
Beta Was this translation helpful? Give feedback.
6 replies
-
Share My Code
#pragma once
#include "nb_span.h"
NAMESPACE_BEGIN(NB_NAMESPACE)
NAMESPACE_BEGIN(detail)
template <>
struct type_caster<std::span<unsigned char>>
: span_numpy_caster<unsigned char> {
};
template <>
struct type_caster<std::span<char>>
: span_numpy_caster<char> {
};
template <>
struct type_caster<std::span<int>>
: span_numpy_caster<int> {
};
template <>
struct type_caster<std::span<unsigned int>>
: span_numpy_caster<unsigned int> {
};
template <>
struct type_caster<std::span<size_t>>
: span_numpy_caster<size_t> {
};
template <>
struct type_caster<std::span<float>>
: span_numpy_caster<float> {
};
template <>
struct type_caster<std::span<double>>
: span_numpy_caster<double> {
};
template <>
struct type_caster<std::span<HKMATH::HKVector2>>
: span_numpy_vec_caster<HKMATH::HKVector2, float, 2> {
};
template <>
struct type_caster<std::span<HKMATH::HKVector3>>
: span_numpy_vec_caster<HKMATH::HKVector3, float, 3> {
};
template <>
struct type_caster<std::span<HKMATH::HKVector4>>
: span_numpy_vec_caster< HKMATH::HKVector4, float, 4> {
};
template <>
struct type_caster<std::span<HKMATH::HKVector4I>>
: span_numpy_vec_caster<HKMATH::HKVector4I, int, 4> {
};
template <>
struct type_caster<std::span<HKMATH::HKMatrix>>
: span_numpy_mat_caster< HKMATH::HKMatrix, float, 4, 4> {
};
template <typename ElementType> struct type_caster<std::span<ElementType>>
: span_list_caster<std::span<ElementType>, ElementType> { };
NAMESPACE_END(detail)
NAMESPACE_END(NB_NAMESPACE)
#pragma once
#include <nanobind/nanobind.h>
#include <nanobind/ndarray.h>
#include <span>
NAMESPACE_BEGIN(NB_NAMESPACE)
NAMESPACE_BEGIN(detail)
template <typename T>
struct span_numpy_caster {
using Span = std::span<T>;
using value_type = T;
using NDArray = ndarray<T, numpy>;
using NDArrayCaster = make_caster<NDArray>;
NB_TYPE_CASTER(Span, NDArrayCaster::Name)
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
make_caster<NDArray> caster;
if (!caster.from_python(src, flags, cleanup))
return false;
const NDArray& array = caster.value;
value = Span((T*)array.data(), array.size());
return true;
}
static handle from_cpp(const Span& v, rv_policy policy, cleanup_list* cleanup) noexcept {
if (policy == rv_policy::automatic || policy == rv_policy::automatic_reference)
policy = rv_policy::reference;
size_t shape = v.size();
void* data_ptr = const_cast<T*>(v.data());
object owner;
switch (policy) {
case rv_policy::move:
owner = capsule(new Span(std::move(v)), [](void* p) noexcept { delete (Span*)p; });
data_ptr = ((Span*)owner.ptr())->data();
policy = rv_policy::reference;
break;
case rv_policy::reference_internal:
if (cleanup->self()) owner = borrow(cleanup->self());
policy = rv_policy::reference;
break;
default: break;
}
NDArray array(data_ptr, 1, &shape, owner, nullptr);
return NDArrayCaster::from_cpp(array, policy, cleanup);
}
};
// 支持 std::span<Vector>,如 Vector3(float[3]),底层数据是 float
template <typename VecType, typename ScalarType, size_t Dim>
struct span_numpy_vec_caster {
using Span = std::span<VecType>;
using NDArray = ndarray<ScalarType, numpy>;
using NDArrayCaster = make_caster<NDArray>;
NB_TYPE_CASTER(Span, NDArrayCaster::Name)
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
make_caster<NDArray> caster;
if (!caster.from_python(src, flags, cleanup))
return false;
const NDArray& array = caster.value;
if (array.ndim() != 2 || array.shape(1) != Dim)
return false;
const ScalarType* data = static_cast<const ScalarType*>(array.data());
size_t n = array.shape(0);
value = Span((VecType*)(data), n);
return true;
}
static handle from_cpp(const Span& v, rv_policy policy, cleanup_list* cleanup) noexcept {
if (policy == rv_policy::automatic || policy == rv_policy::automatic_reference)
policy = rv_policy::reference;
size_t shape[2] = { v.size(), Dim };
ScalarType* data = const_cast<ScalarType*>(reinterpret_cast<const ScalarType*>(v.data()));
object owner;
switch (policy) {
case rv_policy::move:
owner = capsule(new Span(v), [](void* p) noexcept { delete (Span*)p; });
data = reinterpret_cast<ScalarType*>(((Span*)owner.ptr())->data());
policy = rv_policy::reference;
break;
case rv_policy::reference_internal:
if (cleanup->self()) owner = borrow(cleanup->self());
policy = rv_policy::reference;
break;
default: break;
}
NDArray array(data, 2, shape, owner, nullptr);
return NDArrayCaster::from_cpp(array, policy, cleanup);
}
};
// 支持 std::span<Mat>,如 Mat4x4(float[16]),底层数据是 float
template <typename MatType, typename ScalarType, size_t Rows, size_t Cols>
struct span_numpy_mat_caster {
using Span = std::span<MatType>;
using NDArray = ndarray<ScalarType, numpy>;
using NDArrayCaster = make_caster<NDArray>;
NB_TYPE_CASTER(Span, NDArrayCaster::Name)
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
make_caster<NDArray> caster;
if (!caster.from_python(src, flags, cleanup))
return false;
const NDArray& array = caster.value;
if (array.ndim() != 3 || array.shape(1) != Rows || array.shape(2) != Cols)
return false;
const ScalarType* data = static_cast<const ScalarType*>(array.data());
size_t n = array.shape(0);
value = Span((MatType*)(data), n);
return true;
}
static handle from_cpp(const Span& v, rv_policy policy, cleanup_list* cleanup) noexcept {
if (policy == rv_policy::automatic || policy == rv_policy::automatic_reference)
policy = rv_policy::reference;
size_t shape[3] = { v.size(), Rows, Cols };
ScalarType* data = const_cast<ScalarType*>(reinterpret_cast<const ScalarType*>(v.data()));
object owner;
switch (policy) {
case rv_policy::move:
owner = capsule(new Span(v), [](void* p) noexcept { delete (Span*)p; });
data = reinterpret_cast<ScalarType*>(((Span*)owner.ptr())->data());
policy = rv_policy::reference;
break;
case rv_policy::reference_internal:
if (cleanup->self()) owner = borrow(cleanup->self());
policy = rv_policy::reference;
break;
default: break;
}
NDArray array(data, 3, shape, owner, nullptr);
return NDArrayCaster::from_cpp(array, policy, cleanup);
}
};
std::unordered_map<void*, void*> g_mapPySpanData;
template <typename Entry>
Entry* NewSpanDataFor(void* pPyObject, size_t size)
{
auto it = g_mapPySpanData.find(pPyObject);
if (it != g_mapPySpanData.end())
{
delete[] it->second;
g_mapPySpanData.erase(it);
}
Entry* pSpanData = new Entry[size];
g_mapPySpanData.insert({ pPyObject, pSpanData });
return pSpanData;
}
template <typename List, typename Entry> struct span_list_caster {
NB_TYPE_CASTER(List, io_name(NB_TYPING_SEQUENCE, NB_TYPING_LIST) +
const_name("[") + make_caster<Entry>::Name +
const_name("]"))
using Caster = make_caster<Entry>;
template <typename T> using has_reserve = decltype(std::declval<T>().reserve(0));
bool from_python(handle src, uint8_t flags, cleanup_list* cleanup) noexcept {
size_t size;
PyObject* temp;
/* Will initialize 'size' and 'temp'. All return values and
return parameters are zero/NULL in the case of a failure. */
PyObject** o = seq_get(src.ptr(), &size, &temp);
Entry* pNewData = NewSpanDataFor<Entry>(o, size);
Caster caster;
bool success = o != nullptr;
flags = flags_for_local_caster<Entry>(flags);
for (size_t i = 0; i < size; ++i) {
if (!caster.from_python(o[i], flags, cleanup) ||
!caster.template can_cast<Entry>()) {
success = false;
break;
}
pNewData[i] = caster.operator cast_t<Entry>();
}
value = std::span<Entry>(pNewData, size);
Py_XDECREF(temp);
return success;
}
template <typename T>
static handle from_cpp(T&& src, rv_policy policy, cleanup_list* cleanup) {
object ret = steal(PyList_New(src.size()));
if (ret.is_valid()) {
Py_ssize_t index = 0;
for (auto&& value : src) {
handle h = Caster::from_cpp(forward_like_<T>(value), policy, cleanup);
if (!h.is_valid()) {
ret.reset();
break;
}
NB_LIST_SET_ITEM(ret.ptr(), index++, h.ptr());
}
}
return ret.release();
}
};
NAMESPACE_END(detail)
NAMESPACE_END(NB_NAMESPACE)
|
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Has there been any consideration to adding bindings for std::span or other span-like objects? I'm trying to write a binding and finding it's a challenge to juggle the lifetimes, and wondering if there's any prior art here. list_binding sadly doesn't seem to work well.
Going from python, it seems like there wouldn't be any real way to indicate that C++ owns the data without some kind of wrapper around the span object to free the refcount when the object is destroyed, and going from C++ seems even more dangerous unless you go through and immediately create castings/clones of every held object. Am I missing anything here?
Beta Was this translation helpful? Give feedback.
All reactions