Skip to content

[cppyy] Auto-downcast objects returned through smart pointers#22586

Merged
guitargeek merged 3 commits into
root-project:masterfrom
guitargeek:issue-16210
Jun 15, 2026
Merged

[cppyy] Auto-downcast objects returned through smart pointers#22586
guitargeek merged 3 commits into
root-project:masterfrom
guitargeek:issue-16210

Conversation

@guitargeek

Copy link
Copy Markdown
Contributor

Returning an object by raw pointer already triggers an automatic downcast to its actual (most derived) class, but returning it through a smart pointer (std::unique_ptr, std::shared_ptr) did not: the object was bound as the declared underlying type, so derived-class members were not accessible.

This became more and more of a nuisance as smart pointers become more common in C++ interface.

Therefore, this commit implements automatic downcasting also for returned smart pointers.

Closes #16210.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Test Results

    21 files      21 suites   3d 10h 44m 38s ⏱️
 3 866 tests  3 791 ✅   0 💤 75 ❌
72 615 runs  72 383 ✅ 157 💤 75 ❌

For more details on these failures, see this check.

Results for commit 3a4918e.

♻️ This comment has been updated with latest results.

@guitargeek guitargeek force-pushed the issue-16210 branch 3 times, most recently from aac5660 to 2685f09 Compare June 15, 2026 09:39
Comment thread bindings/pyroot/pythonizations/doc/pythonizations.md
Comment thread bindings/pyroot/pythonizations/doc/pythonizations.md Outdated
Comment thread bindings/pyroot/pythonizations/doc/pythonizations.md Outdated
Returning an object by raw pointer already triggers an automatic
downcast to its actual (most derived) class, but returning it through a
smart pointer (`std::unique_ptr`, `std::shared_ptr`) did not: the object
was bound as the declared underlying type, so derived-class members were
not accessible.

This became more and more of a nuisance as smart pointers become more
common in C++ interface.

Therefore, this commit implements automatic downcasting also for returned
smart pointers.

Closes root-project#16210.
Now that also objects returned by smart pointer get automatically
downcasted to the actual type, we don't need to dereference returned
smart pointer objects to trigger the automatic downcasting.
This commit documents the auto-downcasting for return types of C++
functions, and also improves the structure of the pythonization docs as
follows:

  * The term "pythonizations" is very overloaded, so expand the
    explanation in the beginning a bit

  * Cleanly separate into explanation of pythonization in ROOT, and
    later pythonization of use classes. Before, the section on user
    pythonization was just labeled "Pythonization example", which was
    confusing to me. Will this be an example of a pythonization in ROOT?

  * Move the explanation of the user pythonizations to the end, as this
    is an advanced feature.
@guitargeek

Copy link
Copy Markdown
Contributor Author

Updated! This is how it looks now:

@guitargeek

Copy link
Copy Markdown
Contributor Author

Automatic downcasting pythonization

In C++, it is possible to use a base-class pointer to refer to an instance of a
derived type.

class Base {
public:
   virtual ~Base() = default;
};
class Derived : public Base {};

Base *foo() {
    static Derived obj;
    return &obj;
}

The same is also possible for smart pointers, like std::unique_ptr or
std::shared_ptr. For example:

std::unique_ptr<Base> foo_unique() {
    return std::unique_ptr<Base>{new Derived};
}

Using the Derived interface on the return value is not possible in C++ without
type casting (e.g. a dynamic_cast). Since explicit type casting is not natural
in Python, ROOT attempts to automatically downcast raw pointer or smart pointer
return values to their actual type. Demonstrating this with the types above:

p1 = ROOT.foo()
p2 = ROOT.foo_unique()

# if you absolutely need a base class proxy, there is a way:
p3 = ROOT.bind_object(p1, "Base")

print(p1)
print(p2)
print(p3)

will give you something like:

<cppyy.gbl.Derived object>
<cppyy.gbl.Derived object held by std::unique_ptr<Base>>
<cppyy.gbl.Base object>

Note 1: keep in mind that the auto downcasting also affects overload
resolution. For example, consider these two overloads:

void consume(Base *) {}    // overload 1
void consume(Derived *) {} // overload 2

In C++, consume(foo()) will hit overload 1. In Python,
ROOT.consume(ROOT.foo()) resolves to the second overload because the pointee
was automatically downcast. If you really need to hit the first Base overload, you'll have to explicitly cast back to the base class type with ROOT.bind_object, as shown before.

Note 2: while the type of the pointee gets downcast, smart pointer types
remain unchanged. That's because std::unique_ptr<Base> and
std::unique_ptr<Derived> are distinct, unrelated types. Template
instantiations don't inherit from one another even when their type arguments do.

Note 3: automatic downcasting is not enabled
unconditionally. It is only available for polymorphic base
classes, which is why the Base class in the example has a virtual destructor.

@guitargeek guitargeek requested a review from vepadulano June 15, 2026 13:55

@vepadulano vepadulano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Really nice, thanks! Make sure the CI has no related failures before merging 👍

@guitargeek guitargeek merged commit a622764 into root-project:master Jun 15, 2026
70 of 83 checks passed
@guitargeek guitargeek deleted the issue-16210 branch June 15, 2026 18:29
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.

[PyROOT] Automatic downcasting of smart pointers to actual type

2 participants