@@ -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+
317422static 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