Home > Blockchain >  Downcasting without being explicit
Downcasting without being explicit

Time:11-23

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.

  • Related