Skip to content

Commit e8735b3

Browse files
committed
[CPyCppyy] Pythonize: add std::span support and no-std wstring aliases [ROOT-patch]
Source: ROOT-master - Register a std::span pythonization that overrides begin() / end() with a JIT-compiled __cppyy_internal::ptr_iterator helper. libstdc++ (GCC >= 15) implements std::span::iterator via a private nested tag type, which CallFunc-generated wrappers cannot name without violating access rules — the pointer-based iterator sidesteps that - Add more std::basic_string name variants to the STLWString pythonization
1 parent 1850362 commit e8735b3

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,111 @@ static ItemGetter* GetGetter(PyObject* args)
314314
return getter;
315315
}
316316

317+
namespace {
318+
319+
void compileSpanHelpers()
320+
{
321+
static bool compiled = false;
322+
323+
if (compiled)
324+
return;
325+
326+
compiled = true;
327+
328+
auto code = R"(
329+
namespace __cppyy_internal {
330+
331+
template <class T>
332+
struct ptr_iterator {
333+
T *cur;
334+
T *end;
335+
336+
ptr_iterator(T *c, T *e) : cur(c), end(e) {}
337+
338+
T &operator*() const { return *cur; }
339+
ptr_iterator &operator++()
340+
{
341+
++cur;
342+
return *this;
343+
}
344+
bool operator==(const ptr_iterator &other) const { return cur == other.cur; }
345+
bool operator!=(const ptr_iterator &other) const { return !(*this == other); }
346+
};
347+
348+
template <class T>
349+
ptr_iterator<T> make_iter(T *begin, T *end)
350+
{
351+
return {begin, end};
352+
}
353+
354+
} // namespace __cppyy_internal
355+
356+
// Note: for const span<T>, T is const-qualified here
357+
template <class T>
358+
auto __cppyy_internal_begin(T &s) noexcept
359+
{
360+
return __cppyy_internal::make_iter(s.data(), s.data() + s.size());
361+
}
362+
363+
// Note: for const span<T>, T is const-qualified here
364+
template <class T>
365+
auto __cppyy_internal_end(T &s) noexcept
366+
{
367+
// end iterator = begin iterator with cur == end
368+
return __cppyy_internal::make_iter(s.data() + s.size(), s.data() + s.size());
369+
}
370+
)";
371+
Cppyy::Compile(code, /*silent*/ true);
372+
}
373+
374+
PyObject *spanBegin()
375+
{
376+
static PyObject *pyFunc = nullptr;
377+
if (!pyFunc) {
378+
compileSpanHelpers();
379+
PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::GetGlobalScope());
380+
pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_begin");
381+
if (!pyFunc) {
382+
PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
383+
"'__cppyy_internal_begin' for std::span pythonization");
384+
}
385+
}
386+
return pyFunc;
387+
}
388+
389+
PyObject *spanEnd()
390+
{
391+
static PyObject *pyFunc = nullptr;
392+
if (!pyFunc) {
393+
compileSpanHelpers();
394+
PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::GetGlobalScope());
395+
pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_end");
396+
if (!pyFunc) {
397+
PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper "
398+
"'__cppyy_internal_end' for std::span pythonization");
399+
}
400+
}
401+
return pyFunc;
402+
}
403+
404+
} // namespace
405+
406+
static PyObject *SpanBegin(PyObject *self, PyObject *)
407+
{
408+
auto begin = spanBegin();
409+
if (!begin)
410+
return nullptr;
411+
return PyObject_CallOneArg(begin, self);
412+
}
413+
414+
static PyObject *SpanEnd(PyObject *self, PyObject *)
415+
{
416+
auto end = spanEnd();
417+
if (!end)
418+
return nullptr;
419+
return PyObject_CallOneArg(end, self);
420+
}
421+
317422
static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter)
318423
{
319424
Py_ssize_t sz = getter->size();
@@ -1833,6 +1938,18 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope)
18331938

18341939
//- class name based pythonization -------------------------------------------
18351940

1941+
if (IsTemplatedSTLClass(name, "span")) {
1942+
// libstdc++ (GCC >= 15) implements std::span::iterator using a private
1943+
// nested tag type, which makes the iterator non-instantiable by
1944+
// CallFunc-generated wrappers (the return type cannot be named without
1945+
// violating access rules).
1946+
//
1947+
// To preserve correct Python iteration semantics, we replace begin()/end()
1948+
// for std::span to return a custom pointer-based iterator instead.
1949+
Utility::AddToClass(pyclass, "begin", (PyCFunction)SpanBegin, METH_NOARGS);
1950+
Utility::AddToClass(pyclass, "end", (PyCFunction)SpanEnd, METH_NOARGS);
1951+
}
1952+
18361953
if (IsTemplatedSTLClass(name, "vector")) {
18371954

18381955
// std::vector<bool> is a special case in C++
@@ -1972,8 +2089,14 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope)
19722089
Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLViewStringStr, METH_NOARGS);
19732090
}
19742091

2092+
// The first condition was already present in upstream CPyCppyy. The other two
2093+
// are special to ROOT, because its reflection layer gives us the types without
2094+
// the "std::" namespace. On some platforms, that applies only to the template
2095+
// arguments, and on others also to the "basic_string".
19752096
else if (name == "std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >"
19762097
|| name == "std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >"
2098+
|| name == "basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
2099+
|| name == "std::basic_string<wchar_t,char_traits<wchar_t>,allocator<wchar_t> >"
19772100
) {
19782101
Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLWStringRepr, METH_NOARGS);
19792102
Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLWStringStr, METH_NOARGS);

0 commit comments

Comments
 (0)