Как преобразовать объект базового типа в 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 шт):
Вариант с виртуальными функциями:
#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
}