Общий фабричный механизм в 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).

Я считаю, что на вопрос дан ответ, и, если уж на то пошло, вышеуказанные пункты должны быть отдельными вопросам

Ответы на вопрос(4)

Ваш ответ на вопрос