Overloading function based on pointer type of unique_ptr parameter
Asked Answered
P

1

6

I was under the impression that unique_ptr could infer class hierarchy in the same way that a normal pointer can, but when I try to overload a function like this:

void func(unique_ptr<Derived1>& d1);
void func(unique_ptr<Derived2>& d2);

And then call one of the functions like this:

unique_ptr<Base> b = make_unique<Derived1>();
func(b);    

I get an error saying that no instance of overloaded function "func" matches the argument list. The runtime type of b is Derived1, so I expected that the first overload would be called.

Also, when I return a unique_ptr from a function, the compiler is able to cast (? not sure of the appropriate terminology) a derived class to a base class, so that something like this works:

unique_ptr<Base> create(){
    return make_unique<Derived1>();
}

Often in my code I have variables declared as the result of functions like this. They're declared as base but have derived runtime types, and I would like to pass them into one single overloaded function.

How do I overload functions in the same way that I would with regular pointers where class hierarchies are involved?

Prism answered 7/6, 2021 at 11:50 Comment(2)
implicit casting only works going up the inheritance chain towards the top base(s), not the other way around.Ploss
If Derived is (publicly) derived from Base, then a Derived * can be implicitly converted to a Base *. This means a Derived * can be implicitly converted to a Base*, and passed as an argument to a function that accepts a Base *. However, there is no inheritance relationship between a std::unique_ptr<Base> and a std::unique_ptr<Derived>, so a std::unique_ptr<Derived> cannot be implicitly converted to std::unique_ptr<Base>, nor can it be passed as an argument to a function that expects a std::unique_ptr<Base>.Rennin
C
8

You are right that the implicit conversion operations of all standard smart pointers model those of raw pointers. This allows the second snippet to compile, i.e.

unique_ptr<Base> create(){
    return make_unique<Derived1>();
}

However, there is a misconception about the first snippet, as there is never a builtin implicit downcast-like conversion from base class to derived class. Using normal pointers,

void func(Derived1* d1);
void func(Derived2* d2);

Base* b =  new Derived1();
func(b);

won't compile either. This makes sense in a the fundamental OOP sense - look at objects in an inheritance hierarchy through the base class interface, and hide the actual concrete runtime type.

If you need a design with that kind of dispatch, you want to read about the "Visitor" design pattern, which implements a technique called double (or multiple) dispatch. But the amount of boilerplate necessary to realize that gives you a hint why the language doesn't provide this kind of dispatch as a builtin.

Cholent answered 7/6, 2021 at 11:56 Comment(6)
Oh, I swear I had implemented something similar with raw pointers in the past, I must have dreamt it. Cheers!Prism
Amount of boilerplate with std::visit and std::variant is limited.Myocarditis
I had a second thought, what if I implemented a third method which accepts the base class as a parameter, void func(Base* b1); will func(b) call this method even if b's runtime type is derived?Prism
Well yeah sure that works. The dispatch is then completely static though. When you have a std::unique_ptr<Base> it will always call the func overload with a Base parameter and so on...Cholent
So what sort of structure or idiom could I use other than the visitor pattern to acheive this kind of 'type checking' without having to resort to dynamic_casting parameters within the function?Prism
Try to make a decision: if you constantly need downcasts, then virtual polymorphism might be the wrong design for your problem. You can then use sum types that don't hide concrete instances as @Myocarditis suggested. Otherwise, work out something where the base class interface works well for the majority of use cases (and not concrete subclass type information is required for clients to do their work).Cholent

© 2022 - 2024 — McMap. All rights reserved.