c ++ generi c лямбда класса шаблона с абстрактным родителем - PullRequest
0 голосов
/ 08 мая 2020

У меня следующая ситуация:

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, поскольку я не могу иметь статическую / глобальную переменную этого типа, очевидно, поскольку это полностью другая область действия. И не говоря уже о том, что мне пришлось бы делать это для каждого типа посетителей, которые хотели бы ввести другие переменные в функцию посещения, потому что я не могу использовать лямбда-выражения со списками захвата, как это было в моем первоначальном плане.

Как видите, это решение вовсе не идеальное, это просто идея. Надеюсь, кто-нибудь сможет придумать лучший подход или подсказать мне что-нибудь еще.

...