I have a Base
which has two child classes. bar
creates a unique_ptr
who's type is Base
and attempts to initialize it with one of its child classes.
Certainly I can't downcast without being explicit of the type which I don't want to do. Are there ways around it?
struct Base
{
};
struct A : public Base
{
int val;
};
struct B : public Base
{
int val;
};
struct C : public Base
{
// does not have val
};
void bar(bool x, int value)
{
std::unique_ptr<Base> ptr;
if (x)
{
ptr = std::make_unique<A>();
}
else
{
ptr = std::make_unique<B>();
}
ptr->val = value; // ERROR
}
CodePudding user response:
Types in C don't work this way.
If *ptr
needs to have val
, this must be reflected in the static type of *ptr
.
In your case the static type of *ptr
is Base
. There is no val
there.
Data flow analysis may prove that the dynamic type of *ptr
always has val
, but that's irrelevant. The implementation doesn't do data flow analysis. It only looks at static types.
If you need some descendants of Base
to have val
and others not, create an intermediate class BaseWithVal
and work with that.
CodePudding user response:
You can use a generic lambda or helper function template to avoid code duplication:
void bar(bool x, int value)
{
auto make_ptr = [&]<typename T>(){
auto ptr = std::make_unique<T>();
ptr->val = value;
return ptr;
};
std::unique_ptr<Base> ptr;
if (x)
{
ptr = make_ptr.operator()<A>();
}
else
{
ptr = make_ptr.operator()<B>();
}
}
This requires C 20 for the explicit template parameter on the lambda. Before C 20 a helper function is easier to manage, although the following alternative also works:
template<typename T>
struct type_identity {
using type = T;
};
void bar(bool x, int value)
{
auto make_ptr = [&](auto t){
using T = typename decltype(t)::type;
auto ptr = std::make_unique<T>();
ptr->val = value;
return ptr;
};
std::unique_ptr<Base> ptr;
if (x)
{
ptr = make_ptr(type_identity<A>{});
}
else
{
ptr = make_ptr(type_identity<B>{});
}
}
But as the other answer says, if there is a need to uniformly access a subset of derived classes with a val
member, then these should very likely share an intermediate base class with that member. That way you can simply replace std::unique_ptr<Base>
with std::unique_ptr<IntermediateBase>
and everything will work.