First of all, I want to point out that it is the first time I am using dynamic polymorphism and the composite design pattern.
I would like to use the composite design pattern to create a class Tree
which is able to take different objects of the type Tree
, a composite type, or Leaf
, an atomic type. Both Tree and Leaf inherit from a common class Nature
. Tree can store Leaf or Tree objects into a std::vector<std::shared_ptr<Nature>> children
. I would like to fill the vector children
with a syntax of this kind (so I guess I have to use variadic, to consider a generic number of inputs in the input lists), as in the following:
Leaf l0(0);
Leaf l1(1);
Tree t0;
Tree t1;
t0.add(l0,l1);
t1.add(t0,l0,l1); // or in general t1.add(t_00,...,t_0n, l_00,...,l_0n,t10,...,t1n,l10,...,l1n,.... )
Then I would also access different elements of a Tree
by means of the operator[ ]
. So for example t1[0]
returns t0
and t1[0][0]
returns l0
, while t1[0][1]
returns l0
.
Also I would like an homogeneous behaviour. So either use ->
or the dot for accessing the methods on all levels (tree or leaf).
Is it possible to achieve this behaviour?
The implementation of such classes can be like the following:
class Nature
{
public:
virtual void nature_method() = 0;
virtual~Nature();
//virtual Nature& operator[] (int x);
};
class Leaf: public Nature
{
int value;
public:
Leaf(int val)
{
value = val;
}
void nature_method() override
{
std::cout << " Leaf=="<<value<<" ";
}
};
class Tree: public Nature
{
private:
std::vector <std::shared_ptr< Nature > > children;
int value;
public:
Tree(int val)
{
value = val;
}
void add(const Nature&);
void add(const Leaf& c)
{
children.push_back(std::make_shared<Leaf>(c));
}
void add(const Tree& c)
{
children.push_back(std::make_shared<Tree>(c));
}
void add(std::shared_ptr<Nature> c)
{
children.push_back(c);
}
template<typename...Args>
typename std::enable_if<0==sizeof...(Args), void>::type
add(const Leaf& t,Args...more)
{
children.push_back(std::make_shared<Leaf>(t));
};
template<typename...Args>
typename std::enable_if<0==sizeof...(Args), void>::type
add(const Tree& t,Args...more)
{
children.push_back(std::make_shared<Tree>(t));
};
template<typename...Args>
typename std::enable_if<0<sizeof...(Args), void>::type
add(const Leaf& t,Args...more)
{
children.push_back(std::make_shared<Leaf>(t));
add(more...);
};
template<typename...Args>
typename std::enable_if<0<sizeof...(Args), void>::type
add(const Tree& t,Args...more)
{
children.push_back(std::make_shared<Tree>(t));
add(more...);
};
void nature_method() override
{
std::cout << " Tree=="<< value;
for (int i = 0; i < children.size(); i++)
children[i]->nature_method();
}
}
I could implement the overload operator []
to return a pointer to Nature or a Nature object, like so:
Nature& operator[] (int x) {
return *children[x];
}
std::shared_ptr< Nature > operator[] (int x) {
return children[x];
}
In both cases, the return type is Nature
related. This because it could be a Leaf
or a Tree
, which is not known in advance. But since the return type of the operator has to be known at compile time, I cannot do something else.
However, if the returned type would be Tree
related, I cannot use the operator []
anymore, because I have enforced it to be Nature
.
How can I dynamically choose the return type, Tree
or Leaf
related, of []
? Is there any workaround for this?
I could consider operator []
a virtual method in the Nature class, but still I would no what to make out of this.
I have read about covariant types as well, but I do not know if they would be applicable here.
Thank you.
Aucun commentaire:
Enregistrer un commentaire