В C ++, как я могу создать гетерогенный вектор, содержащий шаблонные объекты variafi c? - PullRequest
0 голосов
/ 31 января 2020

Привет, сообщество StackOverflow!

Я играл на работе с шаблонами variadi c, наследованием и абстрактным фабричным шаблоном, и сейчас изо всех сил стараюсь заставить его работать вместе. Похоже, я дошел до того, что знаю об этих предметах в настоящее время, поэтому, если вы подскажете мне подсказку или пример кода, вы получите всю мою благодарность! Заранее спасибо;)

Вот контекст (мои извинения! Есть несколько строк кода ...):

У меня есть База класс

template<typename... Params>
class P
{
    public:
        virtual void compute(Params&... ps) = 0;
        // other things ...

};

и Производные классы

template<typename... Params>
class AP : public P<Params...>
{
    public:
        void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
    private:
        void _compute(std::string& str) {std::cout << "AP::compute str " << str << std::endl;}
};
using A = AP<std::string>;

template<typename... Params>
class BP : public P<Params...>
{
    public:
        void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
    private:
        void _compute(int& i) {std::cout << "BP::compute i " << i << std::endl;}
};
using B = BP<int>;

До здесь, никаких проблем! Если я сделаю небольшой main(), это будет работать без проблем:

int main()
{
    std::unique_ptr<P<int>> p1 = std::make_unique<B>();
    int i = 15;
    p1->compute(i);

    std::unique_ptr<P<std::string>> p2 = std::make_unique<A>();
    std::string str = "abc";
    p2->compute(str);
}

Однако мы можем добавить еще немного: Base классы для фабрики. (Они будут использоваться с другими классами, отличными от моего класса P ... если вам интересно, почему :))

template<typename Base>
class Creator
{
    public:
        virtual std::unique_ptr<Base> create() = 0;
};

template<class Key, class T>
class Factory
{
    public:
        void store(Key key, std::unique_ptr<Creator<T>>&& creator)
        {
            _crs[key] = std::move(creator);
        }

        std::unique_ptr<T> create(Key key)
        {
            return _crs[key]->create();
        }

    private:
        std::map<Key, std::unique_ptr<Creator<T>>> _crs;
};

и их реализациями, чтобы иметь возможность строить P связанные объекты:

template<typename Derived, typename... Params>
class PCreator : public Creator<P<Params...>>
{
    public:
        std::unique_ptr<P<Params...>> create() override
        {
            return std::make_unique<Derived>();
        }
};

template<typename... Params>
class PFactory : public Factory<std::string, P<Params...>>
{
    public:
        PFactory()
        {
            this->store("int", std::make_unique<PCreator<BP<int>>>);
            this->store("string", std::make_unique<PCreator<AP<std::string>>>);
        }
        // create() and store() methods inherited
};

Если я создаю экземпляр PFactory , компилятор, очевидно, не может выполнять свою работу, потому что ему нужны аргументы шаблона для PFactory , что перенаправьте их на Factory<std::string, P<Params...>>.

Но тогда моя фабрика сможет создать только один "тип" объекта P , который может использовать эти Params. Вот как далеко я зашёл один (и, к сожалению, никто из моих коллег не способен помочь мне ...)

Моя цель - написать что-то вроде этого:

class Thing
{
    const std::array<std::string, 2> a = {"one", "two"};
    public:
        Thing()
        {
            PFactory f;
            for(const auto& e : a)
                _ps[e] = std::move(f.create(e));
        }

        void compute()
        {
            int i = 100;
            std::string str = "qwerty";
            // additional computations...
            _ps["one"]->compute(i);
            // additional computations...
            _ps["two"]->compute(str);
        }

    private:
        std::map<std::string, std::unique_ptr<P>> _ps;
};

Вот Po C Я пытался работать и переделывать на CompilerExplorer и откуда исходники выше.

Любая помощь будет очень признателен!


[Редактировать] Да, я заблуждаюсь, думая, что смогу обмануть компилятор для создания различных сигнатур методов с информацией о времени выполнения.

Сумма решений:

(@ walnut: спасибо!) Позвольте вычислить, возьмите std :: any или что-то в этом роде

Я не очень хорошо знаю std::any, но после rtfm-ing CppReference , он мог бы делать эту работу, принимая тот факт, что мне нужно привести параметр обратно к тому, что мне нужно, чтобы он был в моих производных классах (и иметь дело с исключением). К сожалению, в реальном проекте compute() может принимать более одного параметра (причина, по которой я играл с шаблонами variadi c ... Я не хотел заботиться о количестве или типах параметров в каждом compute метод в каждом производном классе), так что это заставило бы меня создать compute(const std::any&) и compute(const std::any&, const std::any&), и т. д. c.

(@ MaxLanghof: спасибо!) Одно (уродливое) решение - это предоставить все возможные вычислительные перегрузки вручную как виртуальные функции.

Да, ваше право, я тоже нахожу это странным (я бы не стал go настолько «уродливым», но у меня нет более красивого решения пока, так ...), но это работает. Недостаток здесь в том, что я не смогу хранить класс P (и связанные с ним классы) в своей собственной библиотеке, как я хотел вначале разделить задачи («MainProgram», играющий с ) P s происходит от lib :: P ).

(@ MaxLanghof: спасибо!), Чтобы выполнить полное отображение _ps во время компиляции.

У меня недостаточно опыта и знаний в C ++, чтобы достичь такой цели. Мне нужно поработать над этим, и если у кого-то есть определенные c ссылки (я имею в виду: не первая ссылка в Google;)) или примеры, я был бы рад узнать.

Спасибо за ваш ответ на данный момент !


[Редактировать] Привет! Извините за задержку, я только вернулся к этому проекту и большое спасибо за ваши идеи и опыт! Это много значит!

Я немного поработал с @Caleth и с работой @KonstantinStupnik (большое спасибо за ваши примеры: они очень помогли мне понять, что я делаю!) И прибыли к этому моменту с моим тестовым примером: https://gcc.godbolt.org/z/AJ8Lsm, где я выбрал исключение std::bad_any_cast, но не понимаю, почему ...

Я подозреваю, что проблема с передачей по ссылке или способом, которым я использую лямбду, чтобы сохранить метод compute в std::any, но не могу быть уверенным. Я попытался расширить типы, полученные в AnyCallable<void>::operator(), чтобы найти разницу с хранимой функцией в конструкторе P , но мне кажется, что это то же самое.

Я пытался передать &AP::compute до P конструктор, но тогда компилятор не сможет больше определять типы параметров ...

Спасибо всем за ваше время, вашу помощь и ваши советы!

1 Ответ

0 голосов
/ 03 февраля 2020

Вы можете стереть параметры, если вы можете указать их на сайте вызова.

Минимально:

#include <functional>
#include <any>
#include <map>
#include <iostream>

template<typename Ret>
struct AnyCallable
{
    AnyCallable() {}
    template<typename F>
    AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
    template<typename ... Args>
    AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
    template<typename ... Args>
    Ret operator()(Args&& ... args) 
    { 
        return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...); 
    }
    template<typename ... Args>
    Ret compute(Args ... args) 
    { 
        return operator()(std::forward<Args>(args)...); 
    }
    std::any m_any;
};

template<>
struct AnyCallable<void>
{
    AnyCallable() {}
    template<typename F>
    AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
    template<typename ... Args>
    AnyCallable(std::function<void(Args...)> fun) : m_any(fun) {}
    template<typename ... Args>
    void operator()(Args&& ... args) 
    { 
        std::invoke(std::any_cast<std::function<void(Args...)>>(m_any), std::forward<Args>(args)...); 
    }
    template<typename ... Args>
    void compute(Args ... args) 
    { 
        operator()(std::forward<Args>(args)...); 
    }
    std::any m_any;
};

using P = AnyCallable<void>;

void A(std::string& str) {std::cout << "AP::compute i " << str << std::endl;}
void B(int i) {std::cout << "BP::compute i " << i << std::endl;}

class Thing
{
    public:
        Thing(){}

        void compute()
        {
            int i = 100;
            std::string str = "qwerty";
            // additional computations...
            ps["one"].compute<int>(i);
            // additional computations...
            ps["two"].compute<std::string&>(str);
        }

    private:
        std::map<std::string, P> ps = { { "one", B }, { "two", A } };
};

Или со всеми Заводскими

...