2012-01-26 49 views
4

我認爲編寫通用訪問者基類模板是一個簡單的練習。我們的目標是能夠寫C++中的通用訪問者基類模板 - 超載問題

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] 

基本上,問題是,由於不同的訪問()成員函數在不同的類中聲明,他們不被視爲候選人重載只是模棱兩可成員訪問。強制編譯器考慮用於重載解析的繼承函數的一般技巧是在派生類中使用'using'語句重新聲明它們,但在這種情況下這是不可行的,因爲它會破壞它的整個泛型方面。

所以,顯然這並不像我想象的那麼容易。有任何想法嗎?

+0

[在可變參數模板使用聲明】(http://stackoverflow.com/questions/7870498/using-declaration-in-variadic-template) – Xeo

回答

4

編譯器不知道調用哪個基類'visit函數。見this question of mine。因此,正如你所說的,你需要在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; 
}; 

Live example on Ideone(我不得不調整部分規範了一下,因爲GCC 4.5。 1在這個部分有點bug。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. 
+0

尼斯的可能重複!我有GCC 4.5.3,它有關於使用擴展參數包作爲模板參數的錯誤,所以我決定不使用遞歸方法,然後當我遇到這個超載問題時,我忘記了它。 – user450018