Общий шаблон базового класса посетителя в C ++ - проблема перегрузки - PullRequest
4 голосов
/ 26 января 2012

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

typedef visitor<some_base, some_derived1, some_derived2> my_visitor;

... и тогда my_visitor будет типом, функционально эквивалентным

struct my_visitor {
    virtual void visit(some_base&) {}
    virtual void visit(some_derived1&) {}
    virtual void visit(some_derived2&) {}
};

, который я могу унаследовать с действительно полезными производными классами посетителей для этой иерархии типов, которые при необходимости переопределяют разные версии visit (). Я хочу, чтобы он работал для любого числа типов, имеющих любые отношения наследования, и я не хочу использовать какие-либо хаки, которые переопределяют виртуальные функции, используя сравнения type_info. Вот что я придумал:

#include <cstdlib>
#include <iostream>
#include <vector>


/** This is the generic part that would go in a visitor.hpp header. */
template <typename T> struct visitor_base {
    virtual void visit(T&) {};
};

template <typename... T> struct visitor : visitor_base<T>... {};


/** This is the part that is specific to a certain type hierarchy. */
struct base;
struct derived1;
struct derived2;

typedef visitor<base, derived1, derived2> my_visitor;


/** This is the type hierarchy. */
struct base {
    virtual void accept(my_visitor& v) { v.visit(*this); }
};

struct derived1 : base {
    derived1() : i(42) {}
    virtual void accept(my_visitor& v) { v.visit(*this); }
    int i;
};

struct derived2 : base {
    derived2() : f(2.79) {}
    virtual void accept(my_visitor& v) { v.visit(*this); }
    float f;
};


/** These are the algorithms. */
struct print_visitor : my_visitor {
    void visit(base&) { std::cout<<"that was a base."<<std::endl; }
    void visit(derived1& d) { std::cout<<"that was "<<d.i<<std::endl; }
    void visit(derived2& d) { std::cout<<"that was "<<d.f<<std::endl; }
};

struct randomise_visitor : my_visitor {
    void visit(derived1& d) { d.i = std::rand(); }
    void visit(derived2& d) { d.f = std::rand() / float(RAND_MAX); }
};


int main() {
    std::vector<base*> objects { new base, new derived1, new derived2,
                                 new derived2, new base };

    print_visitor p;
    randomise_visitor r;

    for (auto& o : objects) o->accept(p);
    for (auto& o : objects) o->accept(r);
    for (auto& o : objects) o->accept(p);
}

Проблема в том, что это не компилируется. GCC говорит

silly_visitor.cpp: In member function ‘virtual void base::accept(my_visitor&)’:
silly_visitor.cpp:24:42: error: request for member ‘visit’ is ambiguous
silly_visitor.cpp:8:16: error: candidates are: void visitor_base<T>::visit(T&) [with T = derived2]
silly_visitor.cpp:8:16: error:                 void visitor_base<T>::visit(T&) [with T = derived1]
silly_visitor.cpp:8:16: error:                 void visitor_base<T>::visit(T&) [with T = base]

По сути, проблема в том, что поскольку разные функции-члены visit () объявляются в разных классах, они рассматриваются не как кандидаты на разрешение перегрузки, а как неоднозначный доступ к членам. Обычный трюк, заставляющий компилятор учитывать унаследованные функции для разрешения перегрузки, состоит в том, чтобы повторно объявить их в производном классе с помощью операторов «using», но в этом случае это невозможно, так как это разрушит весь его общий аспект.

Так что, очевидно, это было не так просто, как я думал. Есть идеи?

1 Ответ

4 голосов
/ 27 января 2012

Компилятор не знает, какую функцию базового класса 'visit вызвать.См. мой вопрос .Таким образом, как вы правильно сказали, вам нужно сделать функции доступными в классе visitor с объявлениями using.К сожалению, вы не можете просто использовать using visitor_base<T>::visit...;, так как это недопустимый шаблон.Вы должны рекурсивно наследовать одну базу за другой и каждый раз приводить базовый класс visit s в область действия производного класса:

template <typename T>
struct visitor_base {
    virtual void visit(T&) {};
};

template <typename Head, typename... Tail>
struct recursive_visitor_base
  : visitor_base<Head>
  , recursive_visitor_base<Tail...>
{
  using visitor_base<Head>::visit;
  using recursive_visitor_base<Tail...>::visit;
};

template<class T>
struct recursive_visitor_base<T>
  : visitor_base<T>
{
  using visitor_base<T>::visit;
};

template <typename... T>
struct visitor
  : recursive_visitor_base<T...>
{
  using recursive_visitor_base<T...>::visit;
};

Живой пример на Ideone (пришлось немного подкорректировать частичную спецификацию, так как в этой части GCC 4.5.1 немного глючит. Clang компилирует код, показанный в этом ответе, просто отлично).Выход:

that was a base.
that was 42
that was 2.79
that was 2.79
that was a base.
=================
that was a base.
that was 1804289383
that was 8.46931e+08
that was 1.68169e+09
that was a base.
...