У меня следующая ситуация:
template <typename T>
class Derived
{
public:
T m_Data;
};
И мне нужно сохранить их в std::vector
, поэтому я ввел абстрактный родительский класс, например:
class Base
{
public:
virtual unsigned int GetID() const = 0;
};
И сделано Derived
наследовать от Base
:
template <typename T>
class Derived : public Base
{
public:
T m_Data;
unsigned int GetID() const override
{
return something;
}
}
Итак, теперь я могу сохранить их в std::vector
вот так:
std::vector<Base*> m_Bases;
Теперь в какой-то момент выполнения программы мне нужно чтобы перебрать m_Bases
и выбрать несколько на основе некоторого условия и использовать их соответствующие m_Data
, например:
void Foo()
{
for (auto& b : m_Bases)
{
if (some_condition)
{
// Access b->m_Data
}
}
}
Так как я не могу этого сделать, потому что T m_Data
является членом Derived
а не Base
, я ввел вектор посетителей:
std::vector<std::function<void(Base*, std::function<void(std::any)>&)>> m_DerivedVisitors;
Теперь, когда я вставляю новый Derived<T>
в m_Bases
, я также вставляю новую лямбду в m_DerivedVisitors
, чтобы я мог позвонить позже, например так:
template <typename T>
void CreateBase()
m_Bases.push_back(new Derived<T>());
m_DerivedVisitors.push_back([](Base* base, auto visitor)
{
if (dynamic_cast<Derived<T>*>(base) != nullptr)
{
visitor(static_cast<Derived<T>*>(base));
}
});
Теперь Foo
потенциально может получить экземпляр Derived
в общем посетителе c лямбда (я надеюсь) вот так:
void Foo()
{
for (auto& b : m_Bases)
{
if (some_condition)
{
// Access b->m_Data
// Note that I made sure GetID() is returning counting index based on template instantiation
// So I could use it to index into m_Bases as-well as into m_DerivedVisitors
m_DerivedVisitors[b->GetID()](base, [&](auto derivedPtr) {
// Now I can access derivedPtr->m_Data and use outside variable with capture list
});
}
}
}
Теперь, если вы вы можете видеть, что у меня есть несколько проблем с этим кодом. Я не знаю, какого типа должен быть второй аргумент m_DerivedVisitors
. Сейчас он установлен на std::function<void(std::any)>&
, потому что я не знаю тип посетителя, потому что его аргумент должен быть типом шаблона Derived
, поэтому я попытался использовать std::any
, а для самого посетителя я использовал auto
параметр, делающий его общим c лямбда:
std::vector<std::function<void(Base*, std::function<void(std::any)>&)>> m_DerivedVisitors;
...
m_DerivedVisitors[b->GetID()](base, [&](auto derivedPtr) {
// Now I can access derivedPtr->m_Data and use outside variable with capture list
});
...
Я не уверен, как я могу заставить его работать. Буду признателен, если кто-нибудь сможет объяснить, как это можно сделать, на примере кода.
Заранее спасибо.
ОБНОВЛЕНИЕ: Я подумал о решении, которое мне совсем не нравится, но я все равно представлю его, может быть, это поможет кому-то улучшить его или найти другое решение. Мы меняем m_DerivedVisitors
на это:
std::vector<std::function<void(Base*)>> m_DerivedVisitors;
и делаем sh на это следующим образом:
m_DerivedVisitors.push_back([](Base* base)
{
if (dynamic_cast<Derived<T>*>(base) != nullptr)
{
Visit(static_cast<Derived<T>*>(base));
}
});
Обратите внимание, что мы очистили параметр функции auto visitor
и вызываем сейчас a stati c Visit
определяется следующим образом:
template<typename U>
static inline void Visit(U* object)
{
auto x = object->m_Data;
// Now we can do what ever we like with m_Data
}
Проблема, которую я вижу с этим решением, заключается в том, что я не могу указать c с каким посетителем я использовать. Один из способов решить эту проблему - снова изменить m_DerivedVisitors
на следующее:
std::vector<std::function<void(Base*, unsigned int)>> m_DerivedVisitors;
и изменить способ, которым мы sh к нему, вот так:
m_DerivedVisitors.push_back([](Base* base, unsigned int id)
{
if (dynamic_cast<Derived<T>*>(base) != nullptr)
{
switch(id)
{
case 0:
Visit0(static_cast<Derived<T>*>(base));
break;
case 1:
Visit1(static_cast<Derived<T>*>(base));
break;
...
};
}
});
Как вы можете видите, мы должны определить всех посетителей как функции c stati, и мы должны выбрать их с помощью id
. Мы не можем сделать это даже с std::unordered_map
, потому что у нас не может быть std::function
, указывающего на функцию шаблона.
Теперь, когда что-то хочет использовать производный тип для определенной задачи c, мы ' Придется определить функцию c stati, добавить ее в case switch и вызвать ее с соответствующим id
.
Это не единственная проблема. Первоначально в моем коде Foo
также был указан лямбда-аргумент шаблона, который я хотел передать ему m_Data
, вот как:
template <typename Fn>
void Foo(Fn&& fn)
{
for (auto& b : m_Bases)
{
if (some_condition)
{
// Access b->m_Data and pass it to fn
fn(b->m_Data); // Note this doesn't compile; just to demonstrate my intentions
}
}
}
Теперь, чтобы сделать это с решением, которое я придумал, что мне пришлось бы сохранить fn
в некоторой статической / глобальной переменной и получить к ней доступ внутри функции посетителя следующим образом:
static int y = 8;
template<typename U>
static inline void Visit0(U* object)
{
// access y
auto x = object->m_Data;
}
template <typename Fn>
void Foo(Fn&& fn)
{
for (auto& b : m_Bases)
{
if (some_condition)
{
// Access b->m_Data and pass it to fn
y = 5;
m_DerivedVisitors[b->GetID()](b, 0);
}
}
}
Обратите внимание, что это не будет работать для захватов типа шаблона, который не собираюсь работать для моей цели с Fn&& fn
, поскольку я не могу иметь статическую / глобальную переменную этого типа, очевидно, поскольку это полностью другая область действия. И не говоря уже о том, что мне пришлось бы делать это для каждого типа посетителей, которые хотели бы ввести другие переменные в функцию посещения, потому что я не могу использовать лямбда-выражения со списками захвата, как это было в моем первоначальном плане.
Как видите, это решение вовсе не идеальное, это просто идея. Надеюсь, кто-нибудь сможет придумать лучший подход или подсказать мне что-нибудь еще.