Как реализовать полностью универсальный Visitor для иерархии классов в C ++ 1x? - PullRequest
0 голосов
/ 13 апреля 2019

Я хотел бы реализовать полностью общий шаблон Visitor, используя> = C ++ 14, используя шаблонное метапрограммирование.Я уже нашел хороший способ обобщить самого посетителя, но у меня возникают проблемы с определением Visitable.Приведенный ниже код работает, но я бы хотел, чтобы закомментированный код в main также работал;в частности, я хочу иметь возможность иметь коллекцию Visitable и применять Visitor к каждому элементу.

Возможно ли то, что я пытаюсь сделать, даже в C ++?

Things I 'я пробовал:

  • class X : public Visitable<X>Это решает проблему отсутствия подходящего accept метода в X, но приводит к неоднозначностям X/A и X/B, которые компилятор не может разрешить.
  • пустой accept метод в Xбез наследования;работает, но специализированные accept методы в A и B никогда не вызываются.
  • заменяет класс шаблона Visitor на обычный класс с шаблоном функции visit для произвольных типов;на самом деле не меняет семантику, но менее читабельно ИМХО
#include <iostream>
#include <vector>

template <typename I>
class Visitable {
 public:
  template <typename Visitor>
  void accept(Visitor&& v) const {
    v.visit(static_cast<const I&>(*this));
  }
};

template <typename T, typename... Ts>
class Visitor : public Visitor<Ts...> {
 public:
  virtual void visit(const T& t);
};

template<typename T>
class Visitor<T> {
 public:
  virtual void visit(const T& t);
};

struct X {
  // template <typename V> void accept(V&& v) const {};
};

struct A : public X, public Visitable<A> {};
struct B : public X, public Visitable<B> {};

class MyVisitor : public Visitor<A, B> {
 public:
  void visit(const A& a) override { std::cout << "Visiting A" << std::endl; }
  void visit(const B& b) override { std::cout << "Visiting B" << std::endl; }
};

int main() {
  MyVisitor v {};
  // std::vector<X> elems { A(), B() };
  // for (const auto& x : elems) {
  //  x.accept(v);
  // }
  A().accept(v);
  B().accept(v);
}

Ответы [ 2 ]

0 голосов
/ 13 апреля 2019

В вашем текущем решении есть несколько проблем:

  1. У вас нет полиморфного типа, который может представлять любой доступный тип. Это означает, что у вас нет способа правильно сохранить все ваши значения A и B в коллекции, чтобы вы могли посетить каждый элемент в коллекции. X этого не делает, потому что нет способа требовать, чтобы подкласс X также разделял подклассы экземпляра шаблона класса Visitable.
  2. Вы не можете обработать несоответствие типов посетителей и посещаемых; Вы не можете гарантировать, что все значения в вашей коллекции доступны для посещения некоторым типом посетителей, просто не делая коллекцию vector<A> или vector<B>, и в этом случае вы теряете возможность хранить значения различных доступных типов в одной коллекции. Вам либо нужен способ обработки во время выполнения сценария несоответствия посетителя / посетителя, либо вам нужна гораздо более сложная структура шаблона.
  3. Нельзя хранить полиморфные значения непосредственно в коллекции. Это связано с тем, что vector последовательно хранит свои элементы в памяти и поэтому должен принимать определенный постоянный размер для каждого элемента; по своей природе полиморфные значения имеют неизвестный размер. Решение состоит в том, чтобы использовать набор (умных) указателей для ссылки на полиморфные значения в других местах кучи.

Вот рабочая адаптация вашего исходного кода:

#include <iostream>
#include <vector>
#include <memory>

template<typename T>
class Visitor;

class VisitorBase {
public:
    virtual ~VisitorBase() {}
};

class VisitableBase {
public:
    virtual void accept(VisitorBase& v) const = 0;
    virtual ~VisitableBase() {}
};

template <typename I>
class Visitable : public VisitableBase {
public:
    virtual void accept(VisitorBase& v) const {
        auto visitor = dynamic_cast<Visitor<I> *>(&v);
        if (visitor == nullptr) {
            // TODO: handle invalid visitor type here
        } else {
            visitor->visit(dynamic_cast<const I &>(*this));
        }
    }
};

template<typename T>
class Visitor : public virtual VisitorBase {
public:
    virtual void visit(const T& t) = 0;
};

struct A : public Visitable<A> {};
struct B : public Visitable<B> {};

class MyVisitor : public Visitor<A>, public Visitor<B> {
public:
    void visit(const A& a) override { std::cout << "Visiting A" << std::endl; }
    void visit(const B& b) override { std::cout << "Visiting B" << std::endl; }
};

int main() {
    MyVisitor v {};
    std::vector<std::shared_ptr<VisitableBase>> elems {
        std::dynamic_pointer_cast<VisitableBase>(std::make_shared<A>()),
        std::dynamic_pointer_cast<VisitableBase>(std::make_shared<B>())
    };
    for (const auto& x : elems) {
        x->accept(v);
    }
    A().accept(v);
    B().accept(v);
}
0 голосов
/ 13 апреля 2019
struct empty_t{};
template <class I, class B=empty_t>
class Visitable:public B {
public:

// ...

struct X : Visitable<X>{
};

struct A : Visitable<A,X> {};
struct B : Visitable<B,X> {};

Обратите внимание, что отправка здесь является статической. И ваш вектор содержит X с, а не A с или B с.

Вы, вероятно, хотите

template <class Visitor>
struct IVisitable {
  virtual void accept(Visitor const& v) const = 0;
protected:
  ~IVisitable(){}
};

template <class I, class Visitor, class B=IVisitable<Visitor>>
struct Visitable {
  virtual void accept(Visitor const& v) const override {
    v.visit(static_cast<const I&>(*this));
  }
};

, который становится ближе.

struct A; struct B; struct X;
struct X:Visitable<X, Visitor<A,B,X>> {
};

struct A :Visitable<A, Visitor<A,B,X>, X> {};
struct B :Visitable<B, Visitor<A,B,X>, X> {};

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

Сделайте его вектором уникальных ptrs для X и добавьте virtual ~X(){} и несколько * и make_unique s, и это будет делать то, что вы хотите.

...