Reputation: 43
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
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!
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;
DerivedA
or DerivedB
onlyYou 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
.
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. */)));
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
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
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
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
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
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