Использование шаблона посетителя с производными от шаблона классами

Я пытаюсь реализовать шаблон посетителя с шаблонными производными классами.

Я работаю с gcc 4.5

вот VisitorTemplate.hpp, я специализировал Derived в классе Visitor, но я хотел бы иметь возможность обрабатывать любой тип:

редактировать: благодаря предложениям interjay, теперь код компилируется и работает без ошибок

#ifndef VISITORTEMPLATE_HPP_
#define VISITORTEMPLATE_HPP_

#include <iostream>
#include <string>
using namespace std;

template<class T> Derived;

class Visitor
{
  public:
    virtual void visit(Derived<string> *e) = 0;
};

class Base
{
  public:
    virtual void accept(class Visitor *v) = 0;
};

template<class T>
Derived: public Base
{
  public:
    virtual void accept(Visitor *v)
    {
       v->visit(this);
    }
    string display(T arg)
    {
       string s = "This is : " + to_string(arg);
       return s;
    }
};

class UpVisitor: public Visitor
{
   virtual void visit(Derived<string> *e)
   {
     cout << "do Up on " + e->display("test") << '\n';
   }
};

class DownVisitor: public Visitor
{
   virtual void visit(Derived<string> *e)
   {
     cout << "do Down on " + e->display("test") << '\n';
   }
};

#endif /* VISITORTEMPLATE_HPP_ */

main.cpp

Base* base = new Derived<string>();
Visitor* up = new UpVisitor();
Visitor* down = new DownVisitor();
base->accept(up);
base->accept(down);

Теперь моя цель — использовать Derived в посещении без специализации; к сожалению, посещение - это виртуальный метод, поэтому я не могу его шаблонировать


person codablank1    schedule 24.10.2011    source источник
comment
Какой компилятор вы используете? Я только что передал код вашего примера в Visual C++ 2010, и он вполне успешно скомпилировался, за исключением жалоб на то, что std::to_string не может обрабатывать тип аргумента параметра шаблона. Я не вижу причин в вашем примере кода, по которому он будет жаловаться на неполный тип, поскольку вы не пропустили реализацию единственной чистой виртуальной функции.   -  person Matthew Walton    schedule 24.10.2011
comment
@MatthewWalton: я работаю с gcc 4.5. Тем не менее, всегда полезно указывать строки, на которые ссылаются сообщения компилятора. Я не хочу считать 31 строку в своем браузере.   -  person sbi    schedule 24.10.2011
comment
@sbi doh, я, должно быть, забыл об этом, когда скармливал компилятору код. Тем не менее, я бы не подумал, что этот конкретный тип ошибки является тем, с которым не согласны GCC и VC++.   -  person Matthew Walton    schedule 24.10.2011
comment
хорошо, забудьте мой код, у вас есть идея справиться с этой общей проблемой Vistor?   -  person codablank1    schedule 24.10.2011
comment
Интересный. Кажется, что предварительное объявление class не совсем соответствует templates   -  person iammilind    schedule 24.10.2011


Ответы (2)


From Modern C++ — Разработка универсального программирования и применение шаблонов проектирования — Андрей Александреску

#include <iostream>

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

template <class T, typename R = int>
class Visitor
{
    public:
        virtual R visit(T &) = 0;
};

template <typename R = int>
class BaseVisitable
{
    public:
        typedef R ReturnType;
        virtual ~BaseVisitable() {};
        virtual ReturnType accept(BaseVisitor & )
        {
            return ReturnType(0);
        }
    protected:
        template <class T>
        static ReturnType acceptVisitor(T &visited, BaseVisitor &visitor)
        {
            if (Visitor<T> *p = dynamic_cast< Visitor<T> *> (&visitor))
            {
                return p->visit(visited);
            }
            return ReturnType(-1);
        }

        #define VISITABLE() \
            virtual ReturnType accept(BaseVisitor &v) \
                { return acceptVisitor(*this, v); }
};


/** example of use */
class Visitable1 : public BaseVisitable<int>
{
    /* Visitable accept one BaseVisitor */
    public:
        VISITABLE();
};

class Visitable2 : public BaseVisitable<int>
{
    /* Visitable accept one BaseVisitor */
    public:
        VISITABLE();
};

class VisitorDerived : public BaseVisitor,
        public Visitor<Visitable1, int>,
        public Visitor<Visitable2, int>
{
    public:
        int visit(Visitable1 & c)
        {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
        int visit(Visitable2 & c)
        {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
};

int main(int argc, char **argv)
{
    VisitorDerived visitor;
    Visitable1 visitable1;
    Visitable2 visitable2;

    visitable1.accept(visitor);
    visitable2.accept(visitor);
}

Можно ли избежать dynamic_cast с шаблоном CRTP, например:

#include <iostream>

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

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

template <class Visitable>
class BaseVisitable
{ 
    public:
        template <typename T>
        void accept(T & visitor)
        {
            visitor.visit(static_cast<Visitable &>(*this));
        }
};

/** example of use */
class Visitable1 : public BaseVisitable<Visitable1>
{
};

class Visitable2 : public BaseVisitable<Visitable2>
{
};

class VisitorDerived : public BaseVisitor, 
                       public Visitor<Visitable1>,
                       public Visitor<Visitable2>
{
    public:
        void visit(Visitable1 & c)
        {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
        void visit(Visitable2 & c)
        {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
};

int main(int argc, char **argv)
{
    VisitorDerived visitor;
    Visitable1 visitable1;
    Visitable2 visitable2;

    visitable1.accept<VisitorDerived>(visitor);
    visitable2.accept<VisitorDerived>(visitor);
}
person Tio Pepe    schedule 24.10.2011
comment
Я нахожу это немного сложным; можно ли избежать dynamic_cast ? - person codablank1; 24.10.2011
comment
Да, вы можете использовать шаблон CRTP, например: - person Tio Pepe; 25.10.2011
comment
Как бы вы расширили это, чтобы охватить иерархии, например. DerivedVisitable1, сохраняя static_cast? Возможно ли это без перехода на циклическую реализацию? - person John Neuhaus; 19.11.2015
comment
Как можно изменить этот код, чтобы разрешить контейнер BaseVisitables. Используя первый пример, я мог бы иметь std::vector‹BaseVisitable›, но я не могу использовать второй, поскольку BaseVisitable является шаблонным. - person Leo C Han; 02.08.2017

Ваш класс Derived не может использовать Visitor, потому что он еще не определен (он был объявлен только вперед и, следовательно, является неполным типом).

Вы можете исправить ошибку компиляции, поместив определение Visitor перед Derived. Вам также необходимо предварительно объявить Derived перед определением Visitor:

template <class T> class Derived;

class Visitor {
public:
    virtual void visit(Derived<string> *e) = 0;
};

template <class T>
class Derived : public Base {
    //.... can call Visitor methods here ...
};
person interjay    schedule 24.10.2011