Search code examples
c++variadic-templatestemplate-meta-programming

Selection between classes at runtime for instantiation


I want to construct an object of a type which is selected at runtime based on an enum class value. The classes A and B have multiple constructors with various numbers of arguments which may be used at runtime. I have attempted this problem by trying to write a factory method returning a shared_ptr to the base class. Here is my ill-formed attempt:

enum class Type
{
    A,
    B // C, etc
};

struct Base{};

struct A : public Base
{
    A(const int a) { std::cout << "A::A(int)"; }
    A(double d) { std::cout << "A::A(double)"; }
    A(int a, double d) { std::cout << "A::A(int,double)"; }
};

struct B : public Base
{
    B(int i, double d) { std::cout << "B::B(double)"; }
    B(int a, double d, double dd) { std::cout << "B::B(int,double, double)"; }
};

template <typename... Args>
std::shared_ptr<Base> make_Type(Type t, Args &&...args)
{
    if (t == Type::A)
        return std::make_shared<A>(std::forward<Args>(args)...);
    else if (t == Type::B)
        return std::make_shared<B>(std::forward<Args>(args)...);
    else
        assert(false);
}

No doubt, this doesn't work as the following call triggers a compilation error as the other branch for B doesn't have any constructor with single argument:

make_Type(Type::A,1);

gcc14 returns the following:

error: no matching function for call to 'B::B(int)'

I can create a static version with if constexpr. But how to attempt a runtime version? (Possibly without using typeinfo). Any alternative approach is also welcome.


Solution

  • You might be looking for something like this:

    template <typename T, typename... Args>
    std::shared_ptr<T> safe_make_shared(Args&&... args) {
        if constexpr (std::is_constructible_v<T, Args&&...>) {
            return std::make_shared<T>(std::forward<Args>(args)...);
        } else {
            return nullptr;
        }
    }
    
    
    template <typename... Args>
    std::shared_ptr<Base> make_Type(Type t, Args &&...args)
    {
        switch (t) {
            case Type::A : return safe_make_shared<A>(std::forward<Args>(args)...);
            case Type::B : return safe_make_shared<B>(std::forward<Args>(args)...);
            default: assert(false); return nullptr;
        }
    }
    

    Demo

    The idea is to have a safe_make_shared function that creates the object if the type is actually constructible with the given set of arguments, or returns null otherwise (or it could assert, or throw an exception as a fallback). The important part is that safe_make_shared can be instantiated with any set of parameters, but only attempts to pass the "right" set of parameters to the object's constructor.