#include <iostream>
#include <cstring>
#include <memory>

struct Base { virtual const char *getName() = 0; virtual ~Base() = default; };
struct SE_0 : Base { virtual const char *getName() override { return "SE_0"; } };
struct SE_1 : Base { virtual const char *getName() override { return "SE_1"; } };
struct SE_2 : Base { virtual const char *getName() override { return "SE_2"; } };

enum TypesEnum {
    E__BEGIN = 0,

    E_0 = E__BEGIN,
    E_1,
    E_2,

    E__END
};

template<TypesEnum>
struct Registry {};

template<>
struct Registry<E_0> {
    static constexpr const char *name = "The first type (SE_0)";
    using type = SE_0;
};

template<>
struct Registry<E_1> {
    static constexpr const char *name = "A second type (SE_1)";
    using type = SE_1;
};

template<>
struct Registry<E_2> {
    static constexpr const char *name = "And the last type (SE_2)";
    using type = SE_2;
};

template<TypesEnum CurrentType>
std::unique_ptr<Base> createTypeImpl(const char *name)
{
    if constexpr (CurrentType < E__END) {
        if (strstr(Registry<CurrentType>::name, name)) {
            return std::make_unique<typename Registry<CurrentType>::type>();
        }
        return createTypeImpl<static_cast<TypesEnum>(CurrentType + 1)>(name);
    } else {
        (void)name;  // Silence 'unreferenced formal parameter' warning
        return nullptr;
    }
}

std::unique_ptr<Base> createType(const char *name)
{
    return createTypeImpl<E__BEGIN>(name);
}

int main()
{
    std::cout << "first type: " << createType("first type")->getName() << std::endl;
    std::cout << "second type: " << createType("second type")->getName() << std::endl;
    std::cout << "last type: " << createType("last type")->getName() << std::endl;

    return EXIT_SUCCESS;
}

