C++ 中的通用访问者基类模板 - 重载问题

use*_*018 4 c++ inheritance templates visitor-pattern c++11

我认为编写通用访问者基类模板将是一个简单的练习。目标是能够写

\n\n
typedef visitor<some_base, some_derived1, some_derived2> my_visitor;\n
Run Code Online (Sandbox Code Playgroud)\n\n

...然后让 my_visitor 成为功能上等效于的类型

\n\n
struct my_visitor {\n    virtual void visit(some_base&) {}\n    virtual void visit(some_derived1&) {}\n    virtual void visit(some_derived2&) {}\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

我可以用该类型层次结构的实际有用的派生访问者类继承它,根据需要重写不同的访问()版本。我希望它适用于具有任何继承关系的任意数量的类型,并且我不想使用任何使用 type_info 比较重新实现虚拟函数的技巧。这就是我想出的:

\n\n
#include <cstdlib>\n#include <iostream>\n#include <vector>\n\n\n/** This is the generic part that would go in a visitor.hpp header. */\ntemplate <typename T> struct visitor_base {\n    virtual void visit(T&) {};\n};\n\ntemplate <typename... T> struct visitor : visitor_base<T>... {};\n\n\n/** This is the part that is specific to a certain type hierarchy. */\nstruct base;\nstruct derived1;\nstruct derived2;\n\ntypedef visitor<base, derived1, derived2> my_visitor;\n\n\n/** This is the type hierarchy. */\nstruct base {\n    virtual void accept(my_visitor& v) { v.visit(*this); }\n};\n\nstruct derived1 : base {\n    derived1() : i(42) {}\n    virtual void accept(my_visitor& v) { v.visit(*this); }\n    int i;\n};\n\nstruct derived2 : base {\n    derived2() : f(2.79) {}\n    virtual void accept(my_visitor& v) { v.visit(*this); }\n    float f;\n};\n\n\n/** These are the algorithms. */\nstruct print_visitor : my_visitor {\n    void visit(base&) { std::cout<<"that was a base."<<std::endl; }\n    void visit(derived1& d) { std::cout<<"that was "<<d.i<<std::endl; }\n    void visit(derived2& d) { std::cout<<"that was "<<d.f<<std::endl; }\n};\n\nstruct randomise_visitor : my_visitor {\n    void visit(derived1& d) { d.i = std::rand(); }\n    void visit(derived2& d) { d.f = std::rand() / float(RAND_MAX); }\n};\n\n\nint main() {\n    std::vector<base*> objects { new base, new derived1, new derived2,\n                                 new derived2, new base };\n\n    print_visitor p;\n    randomise_visitor r;\n\n    for (auto& o : objects) o->accept(p);\n    for (auto& o : objects) o->accept(r);\n    for (auto& o : objects) o->accept(p);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

问题是这不能编译。海湾合作委员会说

\n\n
silly_visitor.cpp: In member function \xe2\x80\x98virtual void base::accept(my_visitor&)\xe2\x80\x99:\nsilly_visitor.cpp:24:42: error: request for member \xe2\x80\x98visit\xe2\x80\x99 is ambiguous\nsilly_visitor.cpp:8:16: error: candidates are: void visitor_base<T>::visit(T&) [with T = derived2]\nsilly_visitor.cpp:8:16: error:                 void visitor_base<T>::visit(T&) [with T = derived1]\nsilly_visitor.cpp:8:16: error:                 void visitor_base<T>::visit(T&) [with T = base]\n
Run Code Online (Sandbox Code Playgroud)\n\n

基本上,问题在于,由于不同的访问()成员函数是在不同的类中声明的,因此它们不被视为重载决策的候选者,而只是被视为不明确的成员访问。强制编译器考虑继承函数以进行重载解析的正常技巧是在派生类中使用“using”语句重新声明它们,但在这种情况下这是不可行的,因为它会破坏它的整个通用方面。

\n\n

所以,显然这并不像我想象的那么容易。有任何想法吗?

\n

Xeo*_*Xeo 5

visit编译器不知道要调用哪个基类函数。看我的这个问题。因此,正如您所说,您需要visitor通过声明使函数在类中可用using。遗憾的是,您不能只使用using visitor_base<T>::visit...;,因为这不是有效的模式。您必须递归地从一个基类继承另一个基类,并且每次都将基类visit带入派生类的范围:

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;
};
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)