Прежде всего, я хочу отметить, что я впервые использую динамический полиморфизм и шаблон составного проекта.
Я хотел бы использовать составной шаблон проектирования для создания class Tree
, который может принимать различные объекты типа Tree
, составного типа или Leaf
, атомарного типа. И Дерево, и Лист наследуют от общего класса Nature
. Tree может хранить объекты Leaf или Tree в std::vector<std::shared_ptr<Nature>> children
. Я хотел бы заполнить вектор children
синтаксисом такого рода (так что, я думаю, мне нужно использовать variadic, чтобы учитывать общее количество входов в списках ввода), как показано ниже:
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,.... )
Тогда я бы также получил доступ к различным элементам Tree
с помощью operator[ ]
. Так, например, t1[0]
возвращает t0
, а t1[0][0]
возвращает l0
, а t1[0][1]
возвращает l0
.
Также я хотел бы однородного поведения. Поэтому используйте ->
или точку для доступа к методам на всех уровнях (дерево или лист).
Возможно ли добиться такого поведения?
Реализация таких классов может быть такой:
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();
}
}
Я мог бы реализовать перегрузку operator []
для возврата указателя на Природу или объект Природы, например так:
Nature& operator[] (int x) {
return *children[x];
}
std::shared_ptr< Nature > operator[] (int x) {
return children[x];
}
В обоих случаях тип возврата связан с Nature
. Это потому, что это может быть Leaf
или Tree
, что неизвестно заранее. Но так как тип возврата оператора должен быть известен во время компиляции, я не могу сделать что-то еще.
Однако, если возвращаемый тип будет связан с Tree
, я не смогу больше использовать operator []
, потому что я установил его как Nature
.
Как я могу динамически выбрать тип возврата, Tree
или Leaf
связанный, []
? Есть ли обходной путь для этого?
Я мог бы считать operator []
виртуальным методом в классе Nature, но все равно мне было бы нечего из этого делать.
Я также читал о ковариантных типах, но я не знаю, будут ли они применимы здесь.
Спасибо.