Как преобразовать объект базового типа в variant типов-наследников?

Есть следующая иерархия наследования:

class A // Abstract
{
public:
    virtual ~A() = default;
    int a;
};

class B : public A
{
public:
    int b;
};

class C : public A
{
public:
    int c;
};

Есть необходимость преобразовать объект типа A в std::variant<B*, C*> (A - чисто виртуальный). То есть выполнить приведение к наследуемому типу, при том, что точный тип неизвестен.

Пытаясь решить проблему, я изобрел следующий вариант решения:

template <class TRet, class CurrentType, class... DerivedTypes>
struct caster 
{
    static TRet cast(A* a)
    {
        CurrentType certainA = dynamic_cast<CurrentType>(a);
        if (nullptr == certainA)
            return caster<TRet, DerivedTypes...>::cast(a);
        return certainA;
    }
};

template <class TRet, class CurrentType>
struct caster<TRet, CurrentType>
{
    static TRet cast(A* a)
    {
        CurrentType certainA = dynamic_cast<CurrentType>(a);
        if (nullptr == certainA)
            return (CurrentType)nullptr;
        return certainA;
    }
};

template<class... DerivedTypes>
std::variant<DerivedTypes...> toVariantOfDerivedTypes(A* a)
{
   return caster<std::variant<DerivedTypes...>, DerivedTypes...>::cast(a);
}

Такой вариант успешно отрабатывает, вот пример использования:

int main ()
{
    struct visiter
    {
        void operator ()(B* b)
        {
            std::cout << "bb";
        }
        
        void operator ()(C* c)
        {
            std::cout << "cc";
        }
    };
    
    A* a = new B();
    
    auto var = toVariantOfDerivedTypes<B*, C*>(a);
    std::visit(visiter(), var); // output: bb
    
    return 0;
}

Но мне не нравится использование dynamic_cast, по сути мой вариант решения просто перебирает все типы в поисках того, к которому приведение будет успешно. Я хотел бы каким то образом избавиться от этого и сделать алгоритм менее топорным и более производительным.

Как можно сделать это (желательно средствами стандартной библиотеки C++ 17 или boost)?


Ответы (1 шт):

Автор решения: HolyBlackCat

Вариант с виртуальными функциями:

#include <concepts>
#include <iostream>
#include <variant>

struct B;
struct C;

using Var = std::variant<B *, C *>;

struct A
{
    virtual ~A() = default;

    int a = 0;
    
    virtual Var ToVariant() = 0;
};

template <typename D>
struct Base : A
{
    Var ToVariant() override
    {
        if constexpr (requires{static_cast<D *>(this);})
        {
            assert(typeid(*this) == typeid(D));
            return static_cast<D *>(this);
        }
        else
        {
            D *ret = dynamic_cast<D *>(this);
            assert(ret);
            return ret;
        }
    }
};

struct B : Base<B> {int b = 0;};
struct C : Base<C> {int c = 0;};

struct PrintA
{
    void operator()(const B *b) const {std::cout << "B: a=" << b->a << ", b=" << b->b << '\n';}
    void operator()(const C *c) const {std::cout << "C: a=" << c->a << ", c=" << c->c << '\n';}
};

int main()
{
    B b;
    b.a = 10;
    b.b = 20;

    C c;
    c.a = 30;
    c.c = 40;

    std::visit(PrintA{}, b.ToVariant()); // B: a=10, b=20
    std::visit(PrintA{}, c.ToVariant()); // B: a=30, b=40
}
→ Ссылка