Skip to content

[Python] Re-implement TTree.SetBranchAddress template call in Python#22247

Open
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:_ttree_setbranchaddress
Open

[Python] Re-implement TTree.SetBranchAddress template call in Python#22247
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:_ttree_setbranchaddress

Conversation

@guitargeek
Copy link
Copy Markdown
Contributor

@guitargeek guitargeek commented May 12, 2026

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:

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.

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:

 	 60 - pyunittests-bindings-pyroot-pythonizations-pyroot-pyz-ttree-iterable (Failed)
 	 61 - pyunittests-bindings-pyroot-pythonizations-pyroot-pyz-ttree-setbranchaddress (Failed)

The tests fail as follows:

972/3751 Test   #60: pyunittests-bindings-pyroot-pythonizations-pyroot-pyz-ttree-iterable ..............................***Failed    3.15 sec
test_array_branch (ttree_iterable.TTreeIterable.test_array_branch) ... ERROR
test_basic_type_branch (ttree_iterable.TTreeIterable.test_basic_type_branch) ... ERROR
test_ntuples (ttree_iterable.TTreeIterable.test_ntuples) ... ERROR
test_struct_branch (ttree_iterable.TTreeIterable.test_struct_branch) ... ok

======================================================================
ERROR: test_array_branch (ttree_iterable.TTreeIterable.test_array_branch)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/github/home/ROOT-CI/src/bindings/pyroot/pythonizations/test/ttree_iterable.py", line 83, in test_array_branch
    ds.SetBranchAddress('arrayb', a)
  File "/github/home/ROOT-CI/build/lib/ROOT/_pythonization/_ttree.py", line 277, in _SetBranchAddress
    return func(bname, addr, *args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Could not find "SetBranchAddress<double>" (set cppyy.set_debug() for C++ errors):
  Failed to instantiate "SetBranchAddress<double>(std::string,double*)"

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.

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.
@guitargeek guitargeek force-pushed the _ttree_setbranchaddress branch from acc4711 to d50fbcf Compare May 12, 2026 10:43
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

Test Results

    22 files      22 suites   3d 10h 29m 20s ⏱️
 3 848 tests  3 845 ✅ 0 💤 3 ❌
76 009 runs  76 004 ✅ 0 💤 5 ❌

For more details on these failures, see this check.

Results for commit d50fbcf.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown
Member

@vepadulano vepadulano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@guitargeek
Copy link
Copy Markdown
Contributor Author

Hi @vepadulano! Sure, I can demonstrate the faulty behavior with ROOT master:

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 nullptr

If 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 TTree.h, because we're using precompiled modules.

@guitargeek
Copy link
Copy Markdown
Contributor Author

@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.

Copy link
Copy Markdown
Member

@vepadulano vepadulano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants