У меня есть следующая простая реализация гетерогенного контейнера:
struct Container {
struct HolderBase {
};
template<typename S>
struct Holder : HolderBase {
Holder(S* s) : s_(s) {}
S* s_;
};
template<typename S>
void push_back(S* s) {
h_.push_back(new Holder<S>(s));
}
vector<HolderBase*> h_;
template<typename B>
B* get(int i) {
//magic here
}
};
Вот как это использовать:
struct ElementBase {};
struct Element : ElementBase {};
int main()
{
Container container;
container.push_back(new Element);
ElementBase* elementBase = container.get<ElementBase>(0);
}
Я могу добавить записи любого типа к нему. Но я не могу понять, как реализовать функцию для извлечения элементов, как некоторый тип, который может быть таким же, как запись или базовый класс для него.
То, что мне нужно, кажется виртуальным и шаблонным одновременно, что невозможно.
Кажется невозможным иметь именно так что вы хотите без особой боли и неудобств (например, регистрация всех классов, с которыми вы хотите работать, в каком-то центральном хранилище).
Вот один из способов сделать почти то, что вы хотите, может быть полезным.
class HolderBase
{
public:
virtual ~HolderBase() = default;
template <class X> X* get() { return dynamic_cast<X*>(this); }
};
template <class T>
class Holder : public HolderBase, public T
{
public:
using T::T;
};
Ваш контейнер тогда просто vector<unique_ptr<HolderBase>>
или какие-нибудь указатели, которые вам нравятся.
Тест-драйв:
struct A {
virtual ~A() = default;
A(int a) : a(a) {};
int a;
};
struct B : A {
B(int a, int b) : A(a), b(b) {};
int b;
};
struct C : A {
C(int a, int c) : A(a), c(c) {};
int c;
};int main () {
std::vector<std::unique_ptr<HolderBase>> v;
v.emplace_back(std::make_unique<Holder<B>>(7,40));
v.emplace_back(std::make_unique<Holder<C>>(0,42));
A* a = v[0]->template get<A>();
B* b = v[0]->template get<B>();
C* c = v[0]->template get<C>();
std::cout << a << " " << b << " " << c << "\n";
a = v[1]->template get<A>();
b = v[1]->template get<B>();
c = v[1]->template get<C>();
std::cout << a << " " << b << " " << c << "\n";
}
как реализовать функцию для извлечения элементов, как некоторый тип, который может быть таким же, как запись или базовый класс для него.
Чтобы получить ту же запись, самый простой способ сохранить ваш текущий дизайн — это использовать RTTI.
во-первых, сделайте стирающую тип основу полиморфной:
struct HolderBase { virtual ~HolderBase() = default; };
тогда вы можете просто dynamic_cast:
template<typename B>
B* get(int i) {
if( auto holder = dynamic_cast<Holder<B>*>(h_[i]) )
{
return holder->s_;
}
else
return nullptr;
}
это вернется nullptr
всякий раз, когда динамический тип объекта, на который указывает h_[i]
это неправильный тип. Вы также можете бросить или предоставить бросок get<B&>
перегрузка тоже.
Обратите внимание, что в C ++ 17 у нас уже есть std :: any (созданный в boost.any), который делает то же самое, но со стандартным интерфейсом (который скоро станет идиоматическим), и все детали уже проработаны; поэтому настоятельно рекомендуется использовать его вместо того, чтобы выкатывать свой собственный.
Проблема получения записи в качестве базы сложнее; самое простое решение — передать разрешенные целевые типы в pushback, что-то вроде:
template<typename... T,typename S>
void push_back_as(S* s) {
static_assert( ( std::is_base_of_v<T,S> && ...) );
h_.push_back(new Holder<S,T...>(s)); // where Holder<S,T0,...> inherits from properly defined Holder<S>,Holder<T0>,...
}
или использовать некоторые другие ненавязчивые средства для регистрации целевых типов (например, класс черты).
в противном случае я не думаю, что это в общем-то возможно на данный момент (это будет, когда у нас будет отражение во время компиляции).