[Python] Re-implement TTree.SetBranchAddress template call in Python#22247
[Python] Re-implement TTree.SetBranchAddress template call in Python#22247guitargeek wants to merge 1 commit into
TTree.SetBranchAddress template call in Python#22247Conversation
Previously, when the data type of the address argument could be determined (e.g. for NumPy arrays or `array.array` objects), we relied on cppyy to pick the right template overload of `SetBranchAddress` by indexing with the data type. However, TTree::SetBranchAddress has two template overloads: ```c++ template <class T> int SetBranchAddress(const char *bname, T **add, ...); template <class T> int SetBranchAddress(const char *bname, T *add, ...); ``` Python lacks pointer semantics, so cppyy cannot meaningfully disambiguate between these two candidates and considers both valid. Until now, we happened to be lucky that cppyy tried the `T *` overload first, which is the one we need. As cppyy becomes stricter about overload resolution ambiguity, this will start failing. To avoid relying on this accident, replicate on the Python side what the template overload does internally: look up the `TClass` for the given type, fall back to `TDataType::GetType` via `cppyy.typeid` otherwise, and then call the non-template `SetBranchAddress` overload directly with the resolved class and type arguments.
acc4711 to
d50fbcf
Compare
Test Results 22 files 22 suites 3d 10h 29m 20s ⏱️ For more details on these failures, see this check. Results for commit d50fbcf. ♻️ This comment has been updated with latest results. |
vepadulano
left a comment
There was a problem hiding this comment.
The changes LGTM, but given the description of the PR I think we would need a test that demonstrates the previously faulty behaviour fixed by these changes.
|
Hi @vepadulano! Sure, I can demonstrate the faulty behavior with ROOT import array
import cppyy
cppyy.cppdef("""
template <class T> void foobar(const char *bname, T **add) {}
template <class T> void foobar(const char *bname, T *add) {}
""")
n = array.array("f", [0.0])
cppyy.gbl.foobar["float"]("myfloat", n)It will fail like this: Traceback (most recent call last):
File "/afs/cern.ch/user/r/rembserj/repro.py", line 11, in <module>
cppyy.gbl.foobar["float"]("myfloat", n)
TypeError: Could not find "foobar<float>" (set cppyy.set_debug() for C++ errors):
could not convert argument to buffer or nullptrIf you flip the order of the overloads, it will work, because the first overload is the right one. This is the random behavior that we should not rely on. Note that in the case of ROOT, the order in which the overloads is tried is not necessarily the order in which they are written in |
|
@vepadulano, I have also added a section in the PR description about the test failure in the cppyy upgrade PR that motivated this PR to begin with. |
Previously, when the data type of the address argument could be determined (e.g. for NumPy arrays or
array.arrayobjects), we relied on cppyy to pick the right template overload ofSetBranchAddressby indexing with the data type.However, TTree::SetBranchAddress has two template overloads:
Python lacks pointer semantics, so cppyy cannot meaningfully disambiguate between these two candidates and considers both valid. Until now, we happened to be lucky that cppyy tried the
T *overload first, which is the one we need. As cppyy becomes stricter about overload resolution ambiguity, this will start failing.To avoid relying on this accident, replicate on the Python side what the template overload does internally: look up the
TClassfor the given type, fall back toTDataType::GetTypeviacppyy.typeidotherwise, and then call the non-templateSetBranchAddressoverload directly with the resolved class and type arguments.Motivation for this PR
This fixes test failures that we see in the Cppyy upgrade PR (#21261), for example in this test run on
alma10:The tests fail as follows:
So in case of the CppInterOp-based cppyy, it's selecting the other overload first, causing the tests to fail. This highlights that we should not rely on the "random first-try" overload selection, because the order in which the overloads are tried is not independent of the compiler/interpreter implementation.