I have the following situation:
template <typename T>
class Derived
{
public:
T m_Data;
};
And I need to store them in std::vector
so I've introduced an abstract parent class like so:
class Base
{
public:
virtual unsigned int GetID() const = 0;
};
And made Derived
inherit from Base
:
template <typename T>
class Derived : public Base
{
public:
T m_Data;
unsigned int GetID() const override
{
return something;
}
}
So now I can store them in std::vector
like so:
std::vector<Base*> m_Bases;
Now at some point of program execution I need to iterate over m_Bases
and select a few based on some condition and use their respective m_Data
like so:
void Foo()
{
for (auto& b : m_Bases)
{
if (some_condition)
{
// Access b->m_Data
}
}
}
Since I can't do that because T m_Data
is member of Derived
and not Base
, I introduced a visitor vector:
std::vector<std::function<void(Base*, std::function<void(std::any)>&)>> m_DerivedVisitors;
Now when I insert a new Derived<T>
to m_Bases
I also insert a new lambda to m_DerivedVisitors
so I could call later like so:
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));
}
});
Now Foo
could potentially get a Derived
instance in a visitor generic lambda (I hope) like so:
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
});
}
}
}
Now if you have sharp eyes you can see I have a few problems with this code.
I don't know what type the second argument of m_DerivedVisitors
should be.
Right now it's set to std::function<void(std::any)>&
because I do not know the type of the visitor because it's argument supposed to be the Derived
template type so I tried to use std::any
and for the visitor it-self I used an auto
parameter making it a generic lambda:
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
});
...
I'm not sure how I can make it work.
I'll would be appreciate if somebody could explain how this can be done with a code example.
Thanks in advance.
UPDATE:
I thought of a solution that I don't like at all but I'll present anyway, maybe it could help someone to help me on improving it or finding another solution.
We change m_DerivedVisitors
to this:
std::vector<std::function<void(Base*)>> m_DerivedVisitors;
and we push to it like this:
m_DerivedVisitors.push_back([](Base* base)
{
if (dynamic_cast<Derived<T>*>(base) != nullptr)
{
Visit(static_cast<Derived<T>*>(base));
}
});
Note we scraped the auto visitor
function parameter and we call now a static Visit
method defined as follows:
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
}
The problem I see with this solution is that I can't be specific with what visitor I use. One way to solve this is change m_DerivedVisitors
again to the following:
std::vector<std::function<void(Base*, unsigned int)>> m_DerivedVisitors;
and change the way we push to it like this:
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;
...
};
}
});
As you can see we have to define all visitors as static functions and we have to choose them using an id
. We can't even do this with std::unordered_map
because we can't have std::function
pointing to a template function.
Now whenever something wants to use the derived type for a specific task we'll have to define a static function and add it to the switch case and call it with the appropriate id
.
This is not the only problem.
Originally in my code Foo
was given a template lambda argument as-well that I wanted to pass m_Data
to it, here's how:
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
}
}
}
Now in-order to do that with the solution I came up with I would have to store fn
in some static/global variable and access it inside the visitor function like so:
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);
}
}
}
Note that this would not work for captures of template type which is not going to work for my purpose with Fn&& fn
since I can't have static/global variable of that type obviously since it's a different scope completetly.
And not to mention I would have to do that for every kind of visitor that would want to introduce other variables into the visit function, that is because I can't use lambda with capture lists as my original plan was.
So as you can see this solution is not ideal at all, it's just an idea.
Hopefully someone could think of a better approach or guide me to something else.