40

教材我注意到,您可以通過模板專門化爲函數重載提供您自己的標準庫函數實現,如swap(x,y)。這對任何可以從除賦值轉換外的其他類型受益的類型都很有用,例如STL containers(已知有交換,我知道)。模板專精VS函數重載

我的問題是:

  1. 更棒的是:模板專門給你的專業 交換實現,或者函數重載提供你希望沒有模板的確切 參數?

  2. 爲什麼它更好?或者如果他們是平等的,爲什麼呢?

回答

60

小故事:當你可以超載時,專門在你需要的時候。長篇小說:C++對待專業化和重載的方式非常不同。這可以用一個例子來解釋。

template <typename T> void foo(T); 
template <typename T> void foo(T*); // overload of foo(T) 
template <>   void foo<int>(int*); // specialisation of foo(T*) 

foo(new int); // calls foo<int>(int*); 

現在讓我們換掉最後兩個。

template <typename T> void foo(T); 
template <>   void foo<int*>(int*); // specialisation of foo(T) 
template <typename T> void foo(T*); // overload of foo(T) 

foo(new int); // calls foo(T*) !!! 

編譯器在重載分辨率之前甚至查看專業化。所以,在這兩種情況下,重載解決方案都會選擇foo(T*)。但是,只有在第一種情況下才會發現foo<int*>(int*),因爲在第二種情況下,int*專業化是foo(T)的專門化,而不是foo(T*)


您提到過std::swap。這使事情變得更加複雜。

該標準說您可以將專門化添加到std命名空間。太棒了,所以你有一些Foo類型,它有一個高性能的交換,那麼你只需在std命名空間中專門設置swap(Foo&, Foo&)。沒問題。

但是,如果Foo是模板類呢? C++沒有部分功能的專業化,所以你不能專注於swap。你唯一的選擇是超載,但標準說你不允許在std命名空間中添加重載!

你必須在這一點上兩個選項:

  1. 創建自己的命名空間的swap(Foo<T>&, Foo<T>&)功能,並希望它得到通過ADL找到。我說「希望」,因爲如果標準庫調用像std::swap(a, b);那樣的交換,那麼ADL根本無法工作。

  2. 忽略標準中說不添加重載的部分,並且無論如何都要這樣做。老實說,即使它在技術上是不允許的,但在所有現實的情況下,它都會起作用。

但要記住的一件事是,不能保證標準庫完全可以使用swap。大多數算法使用std::iter_swap,在我看過的一些實現中,它並不總是轉發到std::swap

+0

很好的例子先生 – tenfour

+6

「我說希望,因爲」 ...這一點指出在幾年前的WG21中。所有標準庫實現者都不知道。 – MSalters

+0

精彩的回答,非常感謝。 –

5

您不允許在std命名空間中重載函數,但允許您專門化模板(我記得),所以這是一種選擇。

另一種選擇是在調用非限定交換之前,將swap函數放在與其運行的函數相同的名稱空間中,並將其與using std::swap;相同。

+0

我更喜歡'使用std :: swap;'來'使用名稱空間std;'。 –

+0

您可能會專注於某些模板:「程序可能會將任何標準庫模板的模板專用化添加到名稱空間std中,只有當聲明依賴於用戶定義的類型並且專業化符合原始模板的標準庫要求而不是明確禁止。「_ [n3337; 17.6.4.2.1/1] 因此,您可以針對不在'std'中的類型專門化'std :: swap'。 – boycy

+0

爲什麼你不能重載std函數?是壞習慣,但std命名空間並不特殊。我不喜歡它,但許多產品爲std命名空間中的類型添加std swap。 – Nick

10

彼得亞歷山大的答案几乎沒有添加。讓我僅僅提到一個使用這個功能的專業化可能會優於超載:如果您必須在沒有參數的函數中選擇

例如

template<class T> T zero(); 
template<> int zero() { return 0; } 
template<> long zero() { return 0L; } 

要使用函數重載做同樣的事情,你將有一個參數添加到函數簽名:

int zero(int) { return 0; } 
long zero(long) { return 0L; } 
+1

有趣的用法:) –