You need to unwrap the raw pointer out of the unique_ptr first:
auto walkers = static_cast<Walker*>(ptr.get());
Note that the ownership of the pointer still belongs to ptr, which is a std::unique_ptr<Person> still. You can transfer the ownership like this:
std::unique_ptr<Walker> walkers(static_cast<Walker*>(ptr.release()));
If you want, you can write a function like this
template<typename Target, typename Source>
auto static_cast_unique(std::unique_ptr<Source> ptr) {
return std::unique_ptr<Target>(static_cast<Target*>(ptr.release()));
}
So you can write
auto walkers = static_cast_unique<Walker>(std::move(v1[1]));
Now, some issues with your code:
Person needs a virtual destructor. You have Walker objects owned by std::unique_ptr<Person>s. That's OK in itself, but when those unique_ptrs try to destroy their objects, they will call ~Person, not ~Walker (since that is what their type says to do), and that will be UB. Give Person a virtual ~Person() (and Walker will inherit that virtualness)
class Person { public: virtual ~Person() = default; };
class Walker : public Person {};
std::unique_ptr(new ...) is better written with std::make_unique
for (int i = 0; i < 4; i++) v1.push_back(std::make_unique<Walker>());
std::unique_ptrs will automatically upcast.
In this case, it seems OK to use static_cast, because you know that the Person* you have actually points to a Walker. However, you will want to use dynamic_cast in the general case, so you can detect when the object isn't of the type you expect. I would use a function like this
template<typename Target, typename Source>
std::unique_ptr<Target> dynamic_cast_unique(std::unique_ptr<Source> &&ptr) {
if(auto ret = dynamic_cast<Target*>(ptr.get())) {
ptr.release();
return std::unique_ptr<Target>(ret);
} else return nullptr;
}
Which is used like
auto walkers = dynamic_cast_unique<Walker>(std::move(v1[1]));
If it succeeds, walkers will own the object previously owned by v1[1] and v1[1] will be empty. Otherwise, walkers will be empty and v1[1] will be unchanged.