Общий фабричный механизм в C ++ 17
Я хотел бы реализовать универсальный фабричный механизм для набора производных классов, который позволяет мне в общих чертах реализовать не только фабричную функцию для создания объектов этого класса, но и создателей других шаблонных классов, которые принимают в качестве аргументов шаблона один из производных классы.
В идеале решение будет использовать только функции C ++ 17 (без зависимостей).
Рассмотри этот пример
#include <iostream>
#include <string>
#include <memory>
struct Foo {
virtual ~Foo() = default;
virtual void hello() = 0;
};
struct FooA: Foo {
static constexpr char const* name = "A";
void hello() override { std::cout << "Hello " << name << std::endl; }
};
struct FooB: Foo {
static constexpr char const* name = "B";
void hello() override { std::cout << "Hello " << name << std::endl; }
};
struct FooC: Foo {
static constexpr char const* name = "C";
void hello() override { std::cout << "Hello " << name << std::endl; }
};
struct BarInterface {
virtual ~BarInterface() = default;
virtual void world() = 0;
};
template <class T>
struct Bar: BarInterface {
void world() { std::cout << "World " << T::name << std::endl; }
};
std::unique_ptr<Foo> foo_factory(const std::string& name) {
if (name == FooA::name) {
return std::make_unique<FooA>();
} else if (name == FooB::name) {
return std::make_unique<FooB>();
} else if (name == FooC::name) {
return std::make_unique<FooC>();
} else {
return {};
}
}
std::unique_ptr<BarInterface> bar_factory(const std::string& foo_name) {
if (foo_name == FooA::name) {
return std::make_unique<Bar<FooA>>();
} else if (foo_name == FooB::name) {
return std::make_unique<Bar<FooB>>();
} else if (foo_name == FooC::name) {
return std::make_unique<Bar<FooC>>();
} else {
return {};
}
}
int main()
{
auto foo = foo_factory("A");
foo->hello();
auto bar = bar_factory("C");
bar->world();
}
Я ищу механизм, который позволил бы мне реализовать оба вариантаfoo_factory
а такжеbar_factory
без перечисления всех классов, так что их не нужно обновлять после добавления, например,FooD
как дополнительный производный класс. В идеале, различные производные Foo должны были бы как-то «саморегистрароваться», но перечисление их всех в одном центральном месте также приемлем
Редактировать
Некоторые разъяснения, основанные на комментариях / ответах:
В моем случае необходимо вызывать фабрики с (что-то вроде) строкой, так как вызывающие фабрики используют полиморфизм сFoo
/ BarInterface
, то есть они не знают о конкретных производных классах. С другой стороны, в Bar мы хотим использовать шаблонные методы производных классов Foo и облегчить встраивание, поэтому нам действительно нужен шаблонный производныйBar
классы (вместо доступа к объектам Foo через некоторый интерфейс базового класса). Мы можем предположить, что все производные классы Foo определены в одном месте (и поэтому при необходимости допустима ручная регистрация, где мы перечисляем их все один раз в одном и том же месте). Тем не менее, они не знают о существовании Bar, и на самом деле у нас есть несколько разных классов, таких какBarInterface
а такжеBar
. Поэтому мы не можем создавать «объекты-конструкторы» Bar и сохранять их на карте так же, как мы можем сделать это для foo_factory
. Я думаю, что нужна какая-то «карта времени компиляции» (или список) всех производных типов Foo, так что при определении bar_factory компилятор может их перебирать, но я не знаю, как это сделать ...Edit2:
Дополнительные ограничения, которые доказали свою актуальность во время обсуждения:
Шаблоны и шаблоны шаблонов: Foo - это шаблоны (с одним аргументом класса), а Bar - шаблоны шаблонов, принимающие конкретный Foo в качестве аргумента шаблона. Шаблоны Foo не имеют специализаций и имеют одинаковое «имя», поэтому запросы к любому конкретному типу вполне подходят. ОсобенноSpecificFoo<double>::name
всегда действует. Ответ @Julius был расширен, чтобы облегчить это уже. Для @ Yakk то же самое, вероятно, можно сделать (но мне потребуется некоторое время, чтобы разобраться в этом подробнее). Гибкий штрих-код фабрики: Фабрика Бар делает чуть больше, чем просто вызывает конструктор. Он также передает некоторые аргументы и выполняет приведение типов (в частности, он может иметь ссылки Foo, которые должны бытьdynamic_cast
к соответствующему конкретному производному Foo). Поэтому решение, которое позволяет писать этот код в процессе определения bar_factory, кажется мне наиболее читаемым. Ответ @Julius отлично работает здесь, даже если код цикла с кортежами немного многословен. Сделать «единственное место» со списком Foos еще проще: Исходя из ответов, которые я до сих пор считаю, я считаю, что у меня есть список типов foo во время компиляции и способ их перебора. Есть два ответа, которые определяют список типов Foo (или шаблонов) в одном центральном месте (либо сtypes
template или с кортежами), что уже здорово. Однако по другим причинам у меня уже есть в том же центральном месте список вызовов макросов, по одному для каждого foo, например,DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ...
. Может объявлениеFooTypes
Как-нибудь воспользуйся этим, чтобы мне не пришлось их перечислять снова? Я думаю, что такие списки типов не могут быть объявлены итеративно (добавление к уже существующему списку), или это возможно? В отсутствие этого, возможно, с некоторой макромагой это было бы возможно. Может быть, всегда переопределение и, следовательно, добавление к списку препроцессора вDECLARE_FOO
звонки, а затем, наконец, "перебрать цикл", чтобы определитьFooTypes
список типов. В препроцессоре повышения IIRC есть средства для циклического перебора списков (хотя я не хочу повышать зависимость). Для еще немногоcontext
, вы можете думать о другом Foo и его аргументе шаблона как о классах, похожих наEigen::Matrix<Scalar>
и Бар - это функторы затрат, которые будут использоваться с Церерой. Барная фабрика возвращает объекты типаceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>
какceres::CostFunction*
указатели.
Edit3:
На основе ответа @Julius я создал решение, которое работает с барами, которые являются шаблонами, а также шаблонами шаблонов. Я подозреваю, что можно объединитьbar_tmpl_factory
а такжеbar_ttmpl_factory
в одну функцию с использованием шаблонов переменных шаблонов (это вещь?).
СДЕЛАТЬ
combinebar_tmpl_factory
а такжеbar_ttmpl_factory
смыслMaking the "single place" listing the Foos even simpler
сверх может заменить использование кортежей @ Yakk'stypes
template (но таким образом, чтобы функция создателя могла быть встроена в сайт вызова цикла для всех типов foo).Я считаю, что на вопрос дан ответ, и, если уж на то пошло, вышеуказанные пункты должны быть отдельными вопросам