2

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.

7
  • 1
    "No doubt, this doesn't work". I dunno, works fine for me. Show a minimal reproducible example, an example that actually exhibits the problem you need help with. Commented Sep 2, 2024 at 18:29
  • @IgorTandetnik: your first parameter is a compile time type, not defined at runtime. But even that works: godbolt.org/z/sP6zYPW96 Commented Sep 2, 2024 at 18:41
  • @IgorTandetnik It won't work for the case make_Type(Type::A,1) as the other branch now doesn't compile as B doesn't have a compatible constructor taking single arg. Commented Sep 2, 2024 at 19:11
  • 1
    So, include that in the code shown in the question, along with the error message you are seeing. Commented Sep 2, 2024 at 19:12
  • 1
    Your types have different sets of constructor signatures, you know what exact signature you are going to use, but you don't know the type? This doesn't look like a real-life scenario. Commented Sep 2, 2024 at 19:30

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

the safe_make_shared seems to have solved the issue. the catch was the is_constructible_v. Thanks. I will check in detail.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.