2013-09-24 60 views
13

我試圖重載一些模板函數來執行特定的動作,如果我把它用一個給定的類MyClass的或任何派生類MyClassDer。下面是代碼:重載函數和繼承

#include <iostream> 

struct MyClass { 
    virtual void debug() const { 
     std::cerr << "MyClass" << std::endl; 
    }; 
}; 

struct MyClassDer : public MyClass { 
    virtual void debug() const { 
     std::cerr << "MyClassDer" << std::endl; 
    }; 
}; 

template <typename T> void func (const T& t) { 
    std::cerr << "func template" << std::endl; 
} 

void func (const MyClass& myClass) { 
    std::cerr << "func overloaded" << std::endl; 
    myClass.debug(); 
} 


int main(int argc, char **argv) { 
    func (1); 
    MyClass myClass; 
    func (myClass); 
    MyClassDer myClassDer; 
    func (myClassDer); 
} 

輸出是:

func template 
func overloaded 
MyClass 
func template 

func (myClassDer)調用模板函數,而不是void func (const MyClass& myClass)。我能做些什麼來獲得預期的行爲?

感謝

回答

3

您可以使用SFINAE:

#include <type_traits> 

template <typename T> 
void func (const T& t, typename std::enable_if<!std::is_base_of<MyClass, T>::value>::type * = nullptr) { 
    std::cout << "func template" << std::endl; 
} 

template < 
    typename T 
    , typename = typename std::enable_if<std::is_base_of<MyClass, T>::value>::type 
> 
void func (const T& t) { 
    std::cout << "func overloaded" << std::endl; 
    t.debug(); 
} 

如果你沒有C++ 11,升壓提供相同的功能。

Live example

EDIT

這應該工作而不C++ 11(使用升壓):

#include "boost/type_traits.hpp" 

template <typename T> 
void func (const T& t, typename boost::enable_if<!boost::is_base_of<MyClass, T>::value>::type * = 0) { 
    std::cout << "func template" << std::endl; 
} 

template <typename T> 
void func (const T& t, typename boost::enable_if<boost::is_base_of<MyClass, T>::value>::type * = 0) { 
    std::cout << "func overloaded" << std::endl; 
    t.debug(); 
} 
+0

這似乎是正確的解決方案。我將會看到如何通過提升來實現這一點。 – user2811040

+0

@ user2811040'boost :: enable_if'和'boost :: is_base_of'。而已。 – Angew

+0

@ user2811040如果沒有C++ 11,將第二個模板參數也移到指針技巧中。 – Angew

0
MyClass *myClassDer = new MyClassDer; 
func(*myClassDer); 
delete myClassDer; 
+1

任何理由引入動態分配? – Angew

+0

函數重載在編譯時被解析,因此您需要'MyClass'才能夠實現正確的功能。但是爲了運行時多態性,你實際上需要一個「MyClassDer」對象。因此'新'。 – HAL

+0

使用動態分配不會改變任何內容。多態性正常工作與參考... – user2811040

0

你需要爲了調用模板函數中使用多態。你需要參考你的基類:

int main(int argc, char **argv) { 
    func (1); 
    MyClass myClass; 
    func (myClass); 
    MyClassDer myClassDer; 
    MyClass* mc = &myClassDer; 
    func (*mc); 
} 

More polymorphism examples and details here

+1

你可能只是創建一個參考並刪除幾個'*' –

4

爲什麼你的代碼沒有工作:看@大衛出色的解釋。爲了得到它的工作,你可以使用SFINAE(「Substition失敗不是一種Errro)加入一個隱藏的模板參數Requires(名稱僅用於文檔目的)

template < 
    typename T, typename Requires = typename 
    std::enable_if<!std::is_base_of<MyClass, T>::value, void>::type 
> 
void func (const T& t) { 
    std::cerr << "func template" << std::endl; 
} 

這將禁用此模板重載解析無論何時T等於MyClass或從MyClass派生,並且會選擇常規函數(對於該函數,將執行派生到基準的轉換,而模板參數演繹只考慮精確匹配)。並在std::enable_if內添加一些不重疊的過載條件,以便對功能過載進行精細選擇,但要小心,SFINAE很細微!

Live Example

注意:我用C++ 11語法編寫了我的SFINAE,使用函數模板的默認模板參數。在C++ 98中,您需要添加常規默認參數或修改返回類型。

+0

優秀!!!!!! – shofee

0

它,因爲你的重載函數的簽名,

void func (const MyClass& myClass) 
{ 
    std::cerr << "func overloaded" << std::endl; 
    myClass.debug(); 
} 

即它要MyClass作爲其參數,以及要使用它調用MyClassDer。所以在編譯時它解決了另一個重載函數和與之相關的鏈接。由於另一個函數是模板化的,因此編譯器與它鏈接沒有問題。

所以,如果你想通過一個MyClassDer對象,你仍然可以使用多態。

MyClass *myClassDer = new MyClassDer; 
func(*myClassDer); 
+4

不需要'new'任何東西,你可以創建指向局部變量的指針(甚至更好,參考) –

1

多態性發生在運行時間,但選擇一個重載函數發生在編譯時間。

所以,在編譯的時候最好超負荷接受MyClassDer

func<MyClassDer> (const MyClassDer& t) 

而不是

func<MyClass> (const MyClass& t) 

那麼編譯器會選擇第一個。


一種可能解決的問題是:

func(static_cast<MyClass&>(myClassDer)); 
11

這是重載是如何工作的。查找完成後,它會查找模板和函數。然後推導模板類型並開始重載解析。在類型的參數的情況下MyClass兩個candiates是:

void func<MyClass>(MyClass const&); 
void func(MyClass const&); 

哪個都同樣的參數是一個非模板良好匹配,但是第二是優選的。在MyClassDer的情況下:

void func<MyClassDer>(MyClassDer const&); 
void func(MyClass const&); 

在這種情況下,第一比第二個更好的候選者,作爲第二個需要一個派生對基轉換和被拾取。

有不同的方法來直接調度來打你的代碼。最簡單的只是強迫參數的類型爲MyClass,從而退回到原來的情況:

func(static_cast<MyClass&>(myClassDer)); 

雖然簡單,但是這需要到處做,如果你在一個地方忘了,做錯事會叫做。其餘的解決方案是複雜的,你可能想要考慮提供不同的函數名稱是否會更好。

選項之一是使用SFINAE禁用時類型是從MyClass導出的模板:

template <typename T> 
typename std::enable_if<!std::is_base_of<MyClass,MyClassDer>::value>::type 
func(T const & t) { ... } 

在這種情況下,查找後,編譯器將執行類型扣,它會推斷T到如果是MyClassDer,它將評估函數的返回類型(SFINAE也可以應用於另一個模板或函數參數)。 is_base_of將產生falseenable_if將不具有嵌套類型。函數聲明將不合格,編譯器將放棄它,留下的解決方案設置爲單個候選項,即非模板重載。

另一種選擇是提供單個模板接口,並使用標籤分派在內部分派給模板或重載(通過不同的名稱)。這個想法是相似的,你評估模板內部的特徵並用該評估生成的類型調用一個函數。

template <typename T> 
void func_impl(T const&, std::false_type) {...} 
void func_impl(MyClass const&, std::true_type) {...} 

template <typename T> 
void func(T const &x) { 
    func_impl(x,std::is_base_of<MyClass,MyClassDer>::type()); 
} 

還有其他的選擇,但這些是兩個常見的,其餘的都是基於相同的原則。

再次考慮問題是否值得解決方案的複雜性。除非在通用代碼中調用func本身,否則對函數名稱的簡單更改將解決該問題,而不會不必要地增加您或其他維護人員維護時可能會遇到問題的複雜性。

+2

您是在幾秒鐘內輸入了這個巨大的答案嗎? +1 – deepmax

+0

@MM .:更像是幾分鐘......我不是一個緩慢的typer,但我不是Flash或 –

+0

@MM。名稱查找,參數推導和超載解析的神聖三位一體顯得如此頻繁,以至於在編寫模板代碼時,這或多或少地存在於工作記憶中是值得的。見例如Stephan T. Lavavej關於[Core C++]的講座(http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-) – TemplateRex

0

只需將它轉換爲基本類型:

MyClassDer myClassDer; 
func(static_cast<MyClass&>(myClassDer)); 
+0

static_cast 對我來說不是一個好的解決方案,因爲我不能真正期望我的函數的用戶使用這個構造。而且,我的功能實際上是一個操作員! – user2811040

+0

@ user2811040啊。我想你希望你的函數的用戶也能夠自己擴展MyClass。在這種情況下,你似乎已經接受了正確的答案。 –