Как динамически выбрать тип возврата оператора [] в шаблоне составного проекта? - PullRequest
0 голосов
/ 01 мая 2019

Прежде всего, я хочу отметить, что я впервые использую динамический полиморфизм и шаблон составного проекта.

Я хотел бы использовать составной шаблон проектирования для создания 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, но все равно мне было бы нечего из этого делать.

Я также читал о ковариантных типах, но я не знаю, будут ли они применимы здесь.

Спасибо.

1 Ответ

0 голосов
/ 01 мая 2019

Если вы хотите быть типобезопасным, возвращаемое значение [] нужно будет проверять на каждом сайте использования, чтобы определить, является ли оно Tree или Leaf.

Вы также можете выбрать не быть типобезопасным и вызывать неопределенное поведение, если вы используете Leaf способом, который должен быть Tree.

Вне зависимости:

virtual Nature& operator[](std::ptrdiff_t i) {
  throw std::invalid_argument("Not a Tree");
}
virtual Nature const& operator[](std::ptrdiff_t i) const {
  throw std::invalid_argument("Not a Tree");
}

в Nature, затем:

virtual Nature& operator[](std::ptrdiff_t i) final override {
  auto r = children.at((std::size_t)x);
  if (r) return *r;
  throw std::out_of_range("no element there");
}
virtual Nature const& operator[](std::ptrdiff_t i) const final override {
  auto r = children.at((std::size_t)x);
  if (r) return *r;
  throw std::out_of_range("no element there");
}

в Tree.

Это вызовет исключения, когда вы используете [] для неправильного типа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...