Holger
Holger

Reputation: 43

Declare object of derived class without specifying it

There is a base-class Base and two possible derived classes DerivedA and DerivedB. How can I declare a variable without specifying (at the point of declaration) which of the two derived classes will be in use?

I have tried the following example:

#include <iostream>
#include <vector>
#include <memory>
using namespace std;
#include <stdlib.h>


struct Base
{
    int base = 0;
};

struct DerivedA : Base
{
    int x = 1;
};

struct DerivedB : Base
{
    int y = 1;
};


class Test
{
public:
    Test(int a)
    {
    Base TestObj;
    if (a==0)
    {
        DerivedA TestObj; // intention: change type of TestObj to DerivedA
    }
    else
    {
        DerivedB TestObj; // intention: change type of TestObj to DerivedB
    }

    TestObj.base = 7;

    if (a==0)
    {
        TestObj.x = 2;
    }
    else
    {
        TestObj.y = 4;
    }

    myObjs.push_back(make_shared<Base>(TestObj));
    }
private:
    vector<shared_ptr<Base>> myObjs;

};

The compiler will throw an error saying "error: ‘struct Base’ has no member named ‘x’ " (or 'y' respectively).

A trivial solution would be to include everything in the first if (a==0) / else statements using a separate myObjs.push_back call for the two cases. However, I am interested in a solution where I can stay more flexible, ideally by e.g. only changing the line 'Base TestObj;' to something more general. Thanks in advance.

Upvotes: 1

Views: 145

Answers (6)

einpoklum
einpoklum

Reputation: 131666

You can't do exactly what you want: Declaring a variable means specifying its type; and if you want the variable to have the type DerivedA or the type DerivedB, with a decision at run-time - you just can't have that.

But all is not lost!

If you can determine the type at compile-time

You can define the type based on a meta-computation, within the compiler, which produces the type. This is @RSahu's answer:

constexpr bool determine_if_we_need_derived_a() { /* ... compile-time computation ... */ }
using type_i_need = typename type_selector<determine_if_we_need_derived_a()>::type;

If you can't determine the type at compile-time...

... but you know it's either DerivedA or DerivedB only

You can use an std::variant: A fancier, safer, nicer version of a C-style union:

std::variant<DerivedA, DerivedB> get_derived(/*params here*/)
{
    // etc.
    if (condition) { return DerivedA( /* ... */); }
    else           { return DerivedB( /* ... */); }
}

// ...

auto derived_object = get_derived(arg1, arg2, /* etc. */);

but of course you'll need to use visitation afterwards, so as to apply the appropriate action depending on the actual type that's in your derived_object.

... but you don't know much of anything

You probably don't have much choice other than to use a pointer, like @NathanOliver and others suggest:

std::unique_ptr<Base> get_derived(/*params here*/)
{
    // etc.
    if (condition) { return std::make_unique<DerivedA>(/* ... */); }
    else           { return std::make_unique<DerivedB>(/* ... */); }
}

// ...

myObjs.emplace_back(std::move(get_derived(arg1, arg2, /* etc. */)));

... and you're feeling frisky

You can try std::any. You can put anything, of any type in it! Now, it's not the right abstraction for the job, but it's fun to learn about. The catch is, that you'll essentially need to know what type an std::any holds before you can retrieve the value and use it.

Another idea is, instead of storing pointers to values, to store an opaque lambda object (no pointer or anything) in your myObjs (which would become myObjActions or something), then invoke those lambdas when necessary. All of these lambdas will need to have the same type though! ... so in a sense you would only be relegating the problem to what happens within the lambda.

Upvotes: 0

Adrian Mole
Adrian Mole

Reputation: 51845

You can make your code a wee bit more succinct:

class Test
{
  public:
    Test(int a) {
        std::shared_ptr<Base> TestObj;
        if (a == 0) {
            std::shared_ptr<DerivedA> TestA  = std::make_shared<DerivedA>();
            TestA->x = 2;
            TestObj = TestA;
        }
        else {
            std::shared_ptr<DerivedB> TestB = std::make_shared<DerivedB>();
            TestB->y = 4;
            TestObj = TestB;
        }
        TestObj->base = 7;
        myObjs.push_back(TestObj);
    }
  private:
    vector<shared_ptr<Base>> myObjs;
};

Upvotes: 0

R Sahu
R Sahu

Reputation: 206637

If you are willing to make Test a class template, you have the ability to choose the object type based on the value of a.

template <int a>
class Test
{
   TypeSelector<a> TestObj;
   ...
};

where

template <int a> class TypeSelectorHelper;

template <> class TypeSelectorHelper<0>
{
   using type = DerivedA;
}

template <> class TypeSelectorHelper<1>
{
   using type = DerivedB;
}

template <int a> 
using TypeSelector = typename TypeSelectorHelper<a>::type;

Upvotes: 2

fas
fas

Reputation: 1413

I guess you need something like this:

std::shared_ptr<Base> TestObj;
if (a==0)
{
    TestObj = std::make_shared<DerivedA>();
}
else
{
    TestObj = std::make_shared<DerivedB>();
}

TestObj->base = 7;

if (a==0)
{
    static_cast<DerivedA*>(TestObj.get())->x = 2;
}
else
{
    static_cast<DerivedB*>(TestObj.get())->y = 4;
}

myObjs.push_back(std::move(TestObj));

Upvotes: 2

NathanOliver
NathanOliver

Reputation: 180720

If you want to keep the flow the same, then you just need to make TestObj a std::shared_ptr<Base> to begin with. That would make the code

Test(int a)
{
    std::shared_ptr<Base> TestObj;
    if (a==0)
    {
        TestObj = std::make_shared<DerivedA>();
    }
    else
    {
        TestObj = std::make_shared<DerivedB>(); 
    }

    TestObj->base = 7;

    if (a==0)
    {
        static_cast<DerivedA&>(*TestObj).x = 2; // need the cast so you can set the member
    }
    else
    {
        static_cast<DerivedB&>(*TestObj).y = 4; // need the cast so you can set the member
    }

    myObjs.push_back(TestObj);
}

Upvotes: 2

Jesper Juhl
Jesper Juhl

Reputation: 31474

"How can I declare a variable without specifying (at the point of declaration) which of the two derived classes will be in use?" - You cannot.

In C++ every variable has to have a single type at the point of declaration. No way around that.

You can, however, use a pointer to the base class. That will accept being assigned any derived type and can be used to call functions in derived types through virtual/dynamic dispatch.

Upvotes: 2

Related Questions