Привет, сообщество 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 конструктор, но тогда компилятор не сможет больше определять типы параметров ...
Спасибо всем за ваше время, вашу помощь и ваши советы!