From d18b65ec404358c1d77235be3c4da978ba0eee03 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Thu, 1 Oct 2026 19:46:34 +0000 Subject: [PATCH 1/3] [cppjit] Treat long and long long buffer formats as interchangeable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit numpy and other buffer producers canonicalize the PEP 3118 format char of 64-bit integer dtypes (e.g. int64 is always reported as 'l' on 64-bit platforms), but C++ parameters declared as int64_t resolve to 'long long' on macOS and 'long' on Linux. The exact format-char matching in the array converters therefore rejected valid buffers when the resolved type and the canonicalized buffer format differed, breaking e.g. passing numpy int64 arrays to 'const int64_t*' parameters on macOS. Add Utility::FormatCodeCompatible() and use it where buffer formats are matched: Utility::GetBuffer() (standard buffer protocol), CArraySetArg() (low-level views), the 2-dimensional branch of the array converter, and StdSpanConverter (std::span parameters). As before, the item-size check guards against actual size mismatches. Ported from the deleted cppyy bindings (CPyCppyy) to the vendored cppjit cpyrt sources, after PyROOT was rewired from the cppyy stack to cppjit. 🤖 Done with the help of AI --- .../pyroot/cppjit/src/cpyrt/Converters.cxx | 7 +- bindings/pyroot/cppjit/src/cpyrt/Utility.cxx | 3 +- bindings/pyroot/cppjit/src/cpyrt/Utility.h | 18 +++++ bindings/pyroot/cppjit/test/test_datatypes.py | 77 +++++++++++++++++++ 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/bindings/pyroot/cppjit/src/cpyrt/Converters.cxx b/bindings/pyroot/cppjit/src/cpyrt/Converters.cxx index 1cba38cd4afb1..1bef46ff5cf92 100644 --- a/bindings/pyroot/cppjit/src/cpyrt/Converters.cxx +++ b/bindings/pyroot/cppjit/src/cpyrt/Converters.cxx @@ -457,7 +457,7 @@ static inline bool CArraySetArg(PyObject* pyobject, cpyrt::Parameter& para, if (cpyrt::LowLevelView_Check(pyobject)) { auto llview = ((cpyrt::LowLevelView*)pyobject); if (llview->fBufInfo.itemsize != size || - !strchr(llview->fBufInfo.format, tc)) { + !cpyrt::Utility::FormatCodeCompatible(tc, llview->fBufInfo.format)) { PyErr_Format(PyExc_TypeError, "could not convert argument to buffer or nullptr"); return false; @@ -1789,7 +1789,7 @@ bool cpyrt::StdSpanConverter::SetArg(PyObject* pyobject, Parameter& para, memset(&fBufinfo, 0, sizeof(Py_buffer)); if (PyObject_GetBuffer(pyobject, &fBufinfo, PyBUF_FORMAT) == 0) { - if (!strchr(fBufinfo.format, typecode)) { + if (!Utility::FormatCodeCompatible(typecode, fBufinfo.format)) { PyErr_Format(PyExc_TypeError, "buffer has incompatible type: expected '%c' for C++ type " "'%s', but got format '%s'", @@ -1880,7 +1880,8 @@ bool ToArrayFromBuffer(PyObject* owner, void* address, PyObject* ctxt, convOk = true; \ } else if (LowLevelView_Check(pyobject) && \ ((LowLevelView*)pyobject)->fBufInfo.ndim == 2 && \ - strchr(((LowLevelView*)pyobject)->fBufInfo.format, code)) { \ + Utility::FormatCodeCompatible( \ + code, ((LowLevelView*)pyobject)->fBufInfo.format)) { \ para.fValue.fVoidp = ((LowLevelView*)pyobject)->get_buf(); \ para.fTypeCode = 'p'; \ convOk = true; \ diff --git a/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx b/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx index f83d39c27fcd7..e5381fe952dc0 100644 --- a/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx +++ b/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx @@ -1231,8 +1231,7 @@ Py_ssize_t cpyrt::Utility::GetBuffer(PyObject* pyobject, char tc, int size, Py_buffer bufinfo; memset(&bufinfo, 0, sizeof(Py_buffer)); if (PyObject_GetBuffer(pyobject, &bufinfo, PyBUF_FORMAT) == 0) { - if (tc == '*' || - strchr(bufinfo.format, tc) + if (tc == '*' || FormatCodeCompatible(tc, bufinfo.format) // if `long int` and `int` are the same size (on Windows and 32bit // Linux, for example), `ctypes` isn't too picky about the type // format, so make sure both integer types pass the type check diff --git a/bindings/pyroot/cppjit/src/cpyrt/Utility.h b/bindings/pyroot/cppjit/src/cpyrt/Utility.h index acd6936ce7880..4c54a546228c5 100644 --- a/bindings/pyroot/cppjit/src/cpyrt/Utility.h +++ b/bindings/pyroot/cppjit/src/cpyrt/Utility.h @@ -2,6 +2,7 @@ #define CPYRT_UTILITY_H // Standard +#include #include #include #include @@ -73,6 +74,23 @@ bool InitProxy(PyObject* module, PyTypeObject* pytype, const char* name); std::unordered_map const& TypecodeMap(); +// check whether a buffer's format supports the requested type code: exact +// match, or interchangeable same-sized integer types for which buffer +// producers such as numpy canonicalize the format char (e.g. int64 is +// reported as 'l' on 64b platforms, even if the C++ parameter is 'long long', +// as it is for int64_t on macOS) +static inline bool FormatCodeCompatible(char tc, const char* fmt) { + if (std::strchr(fmt, tc)) + return true; + if (sizeof(long long) == sizeof(long int) && + ((tc == 'l' && std::strchr(fmt, 'q')) || + (tc == 'q' && std::strchr(fmt, 'l')) || + (tc == 'L' && std::strchr(fmt, 'Q')) || + (tc == 'Q' && std::strchr(fmt, 'L')))) + return true; + return false; +} + // retrieve the memory buffer from pyobject, return buflength, tc (optional) is // python array.array type code, size is type size, buf will point to buffer, // and if check is true, some heuristics will be applied to check buffer diff --git a/bindings/pyroot/cppjit/test/test_datatypes.py b/bindings/pyroot/cppjit/test/test_datatypes.py index 5e508d32dcee6..92e7ec017a11e 100644 --- a/bindings/pyroot/cppjit/test/test_datatypes.py +++ b/bindings/pyroot/cppjit/test/test_datatypes.py @@ -2777,6 +2777,83 @@ def test55_qt_cache_alias_collision(self): ns.take_int8(101) raises(TypeError, ns.take_int8, "e") + def test56_long_long_long_array_interchange(self): + """Arrays of 64b integers must convert to both 'long' and 'long long' + pointer parameters, as buffer producers do not distinguish them and the + mapping of int64_t differs across platforms.""" + + import array + import cppjit + + try: + import numpy as np + except ImportError: + skip('numpy is not installed') + + if array.array('l', []).itemsize != 8: # 'long' and 'long long' must be same size + skip('test assumes 64b long') + + cppjit.cppdef("""\ + namespace ArrayInterchange { + long long sum_ll(const long long* a, int n) { + long long s = 0; + for (int i = 0; i < n; ++i) s += a[i]; + return s; + } + long long sum_l(const long* a, int n) { return sum_ll((const long long*)a, n); } + unsigned long long sum_ull(const unsigned long long* a, int n) { + unsigned long long s = 0; + for (int i = 0; i < n; ++i) s += a[i]; + return s; + } + unsigned long long sum_ul(const unsigned long* a, int n) { return sum_ull((const unsigned long long*)a, n); } + long long sum_i64(const int64_t* a, int n) { return sum_ll((const long long*)a, n); } + const long* data_l() { static long l[3] = {0, 1, 2}; return l; } + const long long* data_ll() { static long long l[3] = {0, 1, 2}; return l; } + }""") + + ns = cppjit.gbl.ArrayInterchange + n = 3 + isum = sum(range(n)) + + # numpy int64 is canonicalized to 'l' or 'q' by the platform + ia = np.arange(n, dtype=np.int64) + ua = np.arange(n, dtype=np.uint64) + for func in [ns.sum_ll, ns.sum_l, ns.sum_i64]: + assert func(ia, n) == isum + for func in [ns.sum_ull, ns.sum_ul]: + assert func(ua, n) == isum + + # same through the standard 'array' module, which can express both formats + al, aq = array.array('l', range(n)), array.array('q', range(n)) + assert ns.sum_ll(al, n) == isum and ns.sum_ll(aq, n) == isum + assert ns.sum_l(al, n) == isum and ns.sum_l(aq, n) == isum + + # same for low-level views returned to Python with the other type + assert ns.sum_ll(ns.data_l(), n) == isum + assert ns.sum_l(ns.data_ll(), n) == isum + + # same through std::span, if available (needs C++20 and ) + if cppjit.evaluate("""#if __cplusplus >= 202002L && __has_include() + 1 + #else + 0 + #endif""") == 1: + cppjit.cppdef("""\ + #include + namespace ArrayInterchange { + long long sum_span_ll(std::span a) { + long long s = 0; + for (auto v : a) s += v; + return s; + } + long long sum_span_l(std::span a) { + return sum_span_ll(std::span{reinterpret_cast(a.data()), a.size()}); + } + }""") + assert ns.sum_span_ll(ia) == isum and ns.sum_span_l(ia) == isum + assert ns.sum_span_ll(al) == isum and ns.sum_span_ll(aq) == isum + class TestANONENUM: def test01_anonymous_enum_repeated_access(self): From c26d1591a693ff5ce3a2dc39169bb91c9834edbc Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Thu, 1 Oct 2026 19:47:16 +0000 Subject: [PATCH 2/3] Revert "[tmva][sofie] Pass senders/receivers as int64_t pointers in GNN tutorial" This reverts commit bb5fa2785b87304e0ecd11250536ff66b97ad16d. The conversion of numpy int64 arrays to 'const int64_t*' parameters is now fixed in cppyy itself, which treats same-sized 'long' and 'long long' buffer formats as interchangeable, so the explicit cppyy.ll.cast workaround is not needed anymore; the tutorial passes the numpy arrays directly again. --- tutorials/machine_learning/TMVA_SOFIE_GNN.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tutorials/machine_learning/TMVA_SOFIE_GNN.py b/tutorials/machine_learning/TMVA_SOFIE_GNN.py index 121fb14d86ebd..4112effc6fa51 100644 --- a/tutorials/machine_learning/TMVA_SOFIE_GNN.py +++ b/tutorials/machine_learning/TMVA_SOFIE_GNN.py @@ -13,7 +13,6 @@ ## ## \author -import cppyy import time import numpy as np @@ -201,9 +200,6 @@ def c(x): receivers = np.ascontiguousarray(graphData["receivers"], dtype=np.int64) senders = np.ascontiguousarray(graphData["senders"], dtype=np.int64) - i64p = cppyy.ll.cast["const int64_t*"] - receivers_ptr = i64p(receivers.ctypes.data) - senders_ptr = i64p(senders.ctypes.data) latent = self._as_arrays( self.encoder_session.infer(c(graphData["nodes"]), c(graphData["edges"]), c(graphData["globals"])), @@ -214,8 +210,8 @@ def c(x): for _ in range(processing_steps): core_input = tuple(np.concatenate([a, b], axis=1) for a, b in zip(latent0, latent)) latent = self._as_arrays( - self.core_session.infer(c(core_input[0]), c(core_input[1]), c(core_input[2]), receivers_ptr, senders_ptr), - n_nodes, n_edges, + self.core_session.infer(c(core_input[0]), c(core_input[1]), c(core_input[2]), receivers, senders), + n_nodes, n_edges, ) decoded = self._as_arrays( self.decoder_session.infer(c(latent[0]), c(latent[1]), c(latent[2])), n_nodes, n_edges From ed88e95a35d8ff118b71c8476606810b3c0199df Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Sun, 4 Oct 2026 07:48:48 +0000 Subject: [PATCH 3/3] [cppjit] Parse buffer formats properly instead of character search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the ad-hoc buffer-format matching with a small PEP 3118 parser. Formats are reduced to their element type (skipping byte-order prefixes like ctypes' 'fBufInfo.itemsize != size || - !cpyrt::Utility::FormatCodeCompatible(tc, llview->fBufInfo.format)) { + !cpyrt::Utility::BufferFormatCompatible(tc, llview->fBufInfo.format)) { PyErr_Format(PyExc_TypeError, "could not convert argument to buffer or nullptr"); return false; @@ -1789,7 +1789,7 @@ bool cpyrt::StdSpanConverter::SetArg(PyObject* pyobject, Parameter& para, memset(&fBufinfo, 0, sizeof(Py_buffer)); if (PyObject_GetBuffer(pyobject, &fBufinfo, PyBUF_FORMAT) == 0) { - if (!Utility::FormatCodeCompatible(typecode, fBufinfo.format)) { + if (!Utility::BufferFormatCompatible(typecode, fBufinfo.format)) { PyErr_Format(PyExc_TypeError, "buffer has incompatible type: expected '%c' for C++ type " "'%s', but got format '%s'", @@ -1880,7 +1880,7 @@ bool ToArrayFromBuffer(PyObject* owner, void* address, PyObject* ctxt, convOk = true; \ } else if (LowLevelView_Check(pyobject) && \ ((LowLevelView*)pyobject)->fBufInfo.ndim == 2 && \ - Utility::FormatCodeCompatible( \ + Utility::BufferFormatCompatible( \ code, ((LowLevelView*)pyobject)->fBufInfo.format)) { \ para.fValue.fVoidp = ((LowLevelView*)pyobject)->get_buf(); \ para.fTypeCode = 'p'; \ @@ -1908,7 +1908,8 @@ bool ToArrayFromBuffer(PyObject* owner, void* address, PyObject* ctxt, convOk = true; \ } \ } else if (LowLevelView_Check(pyobject) && \ - strchr(((LowLevelView*)pyobject)->fBufInfo.format, code)) { \ + Utility::BufferFormatCompatible( \ + code, ((LowLevelView*)pyobject)->fBufInfo.format)) { \ para.fValue.fVoidp = ((LowLevelView*)pyobject)->get_buf(); \ para.fTypeCode = 'p'; \ convOk = true; \ diff --git a/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx b/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx index e5381fe952dc0..03a0c774680e6 100644 --- a/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx +++ b/bindings/pyroot/cppjit/src/cpyrt/Utility.cxx @@ -1201,6 +1201,139 @@ std::unordered_map const& cpyrt::Utility::TypecodeMap() { return typecodeMap; } +namespace { + +// classification of PEP 3118 format characters +enum class EBufferKind { + kSInt, + kUInt, + kFloat, + kComplex, + kChar, + kWChar, + kBool, + kString, + kVoidPtr, + kUnknown +}; + +struct BufferElem { + EBufferKind kind; + std::size_t size; + char code; // the underlying format character +}; + +BufferElem ClassifyFormatChar(char c) { + switch (c) { + case 'b': + return {EBufferKind::kSInt, sizeof(char), c}; + case 'h': + return {EBufferKind::kSInt, sizeof(short), c}; + case 'i': + return {EBufferKind::kSInt, sizeof(int), c}; + case 'l': + return {EBufferKind::kSInt, sizeof(long), c}; + case 'q': + return {EBufferKind::kSInt, sizeof(long long), c}; + case 'n': + return {EBufferKind::kSInt, sizeof(Py_ssize_t), c}; + case 'B': + return {EBufferKind::kUInt, sizeof(unsigned char), c}; + case 'H': + return {EBufferKind::kUInt, sizeof(unsigned short), c}; + case 'I': + return {EBufferKind::kUInt, sizeof(unsigned int), c}; + case 'L': + return {EBufferKind::kUInt, sizeof(unsigned long), c}; + case 'Q': + return {EBufferKind::kUInt, sizeof(unsigned long long), c}; + case 'N': + return {EBufferKind::kUInt, sizeof(std::size_t), c}; + case 'e': + return {EBufferKind::kFloat, 2, c}; // IEEE 754 half + case 'f': + return {EBufferKind::kFloat, sizeof(float), c}; + case 'd': + return {EBufferKind::kFloat, sizeof(double), c}; + case 'g': + return {EBufferKind::kFloat, sizeof(long double), c}; + case '?': + return {EBufferKind::kBool, sizeof(bool), c}; + case 'c': + return {EBufferKind::kChar, sizeof(char), c}; + case 'u': + return {EBufferKind::kWChar, sizeof(wchar_t), c}; + case 'w': + return {EBufferKind::kWChar, sizeof(char32_t), c}; // UCS-4 + case 's': + case 'p': + return {EBufferKind::kString, 0, c}; + case 'P': + return {EBufferKind::kVoidPtr, sizeof(void*), c}; + default: + return {EBufferKind::kUnknown, 0, c}; + } +} + +// reduce a PEP 3118 format string to its element type, skipping an optional +// byte-order/alignment prefix ("!^", *fmt)) + ++fmt; + while ('0' <= *fmt && *fmt <= '9') + ++fmt; + bool isComplex = *fmt == 'Z'; + if (isComplex) + ++fmt; + if (!*fmt || fmt[1] != '\0') + return false; + + BufferElem elem = ClassifyFormatChar(*fmt); + if (isComplex) { + if (elem.kind != EBufferKind::kFloat) + return false; + elem = {EBufferKind::kComplex, 2 * elem.size, *fmt}; + } + elemOut = elem; + return true; +} + +} // namespace + +//---------------------------------------------------------------------------- +bool cpyrt::Utility::BufferFormatCompatible(char tc, const char* fmt) { + // the requested type code: a PEP 3118 character, or a cppjit-internal + // code ('z' = std::complex, 'Z' = std::complex) + BufferElem want; + if (tc == 'z') + want = {EBufferKind::kComplex, 2 * sizeof(float), tc}; + else if (tc == 'Z') + want = {EBufferKind::kComplex, 2 * sizeof(double), tc}; + else + want = ClassifyFormatChar(tc); + if (want.kind == EBufferKind::kUnknown) + return false; + + BufferElem got; + if (!ParseBufferFormat(fmt, got) || got.kind == EBufferKind::kUnknown) + return false; + + // the struct/array modules express bool-ish values as 'signed char' ('b') + if (want.kind == EBufferKind::kBool && got.code == 'b') + return true; + + // 's' and 'p' are both strings but not interchangeable + if (want.kind == EBufferKind::kString) + return got.code == want.code; + + return want.kind == got.kind && want.size == got.size; +} + //---------------------------------------------------------------------------- Py_ssize_t cpyrt::Utility::GetBuffer(PyObject* pyobject, char tc, int size, void*& buf, bool check) { @@ -1231,18 +1364,7 @@ Py_ssize_t cpyrt::Utility::GetBuffer(PyObject* pyobject, char tc, int size, Py_buffer bufinfo; memset(&bufinfo, 0, sizeof(Py_buffer)); if (PyObject_GetBuffer(pyobject, &bufinfo, PyBUF_FORMAT) == 0) { - if (tc == '*' || FormatCodeCompatible(tc, bufinfo.format) - // if `long int` and `int` are the same size (on Windows and 32bit - // Linux, for example), `ctypes` isn't too picky about the type - // format, so make sure both integer types pass the type check - || (sizeof(long int) == sizeof(int) && - ((tc == 'I' && strchr(bufinfo.format, 'L')) || - (tc == 'i' && strchr(bufinfo.format, 'l')))) - // complex float is 'Zf' in bufinfo.format, but 'z' in single char - || (tc == 'z' && strstr(bufinfo.format, "Zf")) - // allow 'signed char' ('b') from array to pass through '?' (bool as - // from struct) - || (tc == '?' && strchr(bufinfo.format, 'b'))) { + if (tc == '*' || BufferFormatCompatible(tc, bufinfo.format)) { buf = bufinfo.buf; if (check && bufinfo.itemsize != size) { diff --git a/bindings/pyroot/cppjit/src/cpyrt/Utility.h b/bindings/pyroot/cppjit/src/cpyrt/Utility.h index 4c54a546228c5..28186c31f12f0 100644 --- a/bindings/pyroot/cppjit/src/cpyrt/Utility.h +++ b/bindings/pyroot/cppjit/src/cpyrt/Utility.h @@ -2,7 +2,6 @@ #define CPYRT_UTILITY_H // Standard -#include #include #include #include @@ -74,22 +73,13 @@ bool InitProxy(PyObject* module, PyTypeObject* pytype, const char* name); std::unordered_map const& TypecodeMap(); -// check whether a buffer's format supports the requested type code: exact -// match, or interchangeable same-sized integer types for which buffer -// producers such as numpy canonicalize the format char (e.g. int64 is -// reported as 'l' on 64b platforms, even if the C++ parameter is 'long long', -// as it is for int64_t on macOS) -static inline bool FormatCodeCompatible(char tc, const char* fmt) { - if (std::strchr(fmt, tc)) - return true; - if (sizeof(long long) == sizeof(long int) && - ((tc == 'l' && std::strchr(fmt, 'q')) || - (tc == 'q' && std::strchr(fmt, 'l')) || - (tc == 'L' && std::strchr(fmt, 'Q')) || - (tc == 'Q' && std::strchr(fmt, 'L')))) - return true; - return false; -} +// check whether a PEP 3118 buffer format string describes an element type +// compatible with the requested type code: same kind (signed/unsigned +// integer, floating point, complex, ...) and same native size, so e.g. +// 'long' and 'long long' buffers are interchangeable where they coincide. +// The type code may be a PEP 3118 character or a cppjit-internal code: +// 'z' (std::complex) or 'Z' (std::complex). +bool BufferFormatCompatible(char tc, const char* fmt); // retrieve the memory buffer from pyobject, return buflength, tc (optional) is // python array.array type code, size is type size, buf will point to buffer, diff --git a/bindings/pyroot/cppjit/test/test_datatypes.py b/bindings/pyroot/cppjit/test/test_datatypes.py index 92e7ec017a11e..e1aded4e2f8a7 100644 --- a/bindings/pyroot/cppjit/test/test_datatypes.py +++ b/bindings/pyroot/cppjit/test/test_datatypes.py @@ -2854,6 +2854,107 @@ def test56_long_long_long_array_interchange(self): assert ns.sum_span_ll(ia) == isum and ns.sum_span_l(ia) == isum assert ns.sum_span_ll(al) == isum and ns.sum_span_ll(aq) == isum + def test57_buffer_format_parsing(self): + """Buffer-format matching parses PEP 3118 properly: byte-order + prefixes are skipped, the complex marker 'Z' is understood, and + elements match on kind (signed/unsigned/float/complex) and native + size rather than on the exact format character.""" + + import ctypes + import cppjit + + try: + import numpy as np + except ImportError: + skip('numpy is not installed') + + cppjit.cppdef("""\ + #include + namespace BufferFormats { + double sum_d(const double* a, int n) { + double s = 0.; + for (int i = 0; i < n; ++i) s += a[i]; + return s; + } + long sum_l(const long* a, int n) { + long s = 0; + for (int i = 0; i < n; ++i) s += a[i]; + return s; + } + int sum_i(const int* a, int n) { + int s = 0; + for (int i = 0; i < n; ++i) s += a[i]; + return s; + } + int count_true(const bool* a, int n) { + int s = 0; + for (int i = 0; i < n; ++i) if (a[i]) ++s; + return s; + } + std::complex sum_cf(const std::complex* a, int n) { + std::complex s; + for (int i = 0; i < n; ++i) s += a[i]; + return s; + } + const std::complex* data_cf() { + static std::complex c[2] = {{1.f, 1.f}, {1.f, -1.f}}; + return c; + } + }""") + + ns = cppjit.gbl.BufferFormats + + # ctypes exports explicitly-endian formats + assert ns.sum_d((ctypes.c_double * 3)(0.5, 1.5, 1.0), 3) == 3.0 + if ctypes.sizeof(ctypes.c_long) == 8: + assert ns.sum_l((ctypes.c_long * 3)(1, 2, 3), 3) == 6 + # ctypes exports c_longlong as ' + if cppjit.evaluate("""#if __cplusplus >= 202002L && __has_include() + 1 + #else + 0 + #endif""") == 1: + cppjit.cppdef("""\ + #include + namespace BufferFormats { + double sum_span_d(std::span a) { + double s = 0.; + for (auto v : a) s += v; + return s; + } + }""") + assert ns.sum_span_d(np.arange(3, dtype=np.float64)) == 3.0 + raises(TypeError, ns.sum_span_d, + np.array([1+2j, 3+4j], dtype=np.complex128)) + class TestANONENUM: def test01_anonymous_enum_repeated_access(self):