簡體   English   中英

模板專業化和功能重載之間的差異?

[英]Differences between template specialization and overloading for functions?

所以,我知道這兩個代碼之間存在差異:

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

template <>
int inc(const int& t)
{
    return t + 1;
}

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

int inc(const int& t)
{
    return t + 1;
}

我對這兩者之間的功能差異感到困惑。 有人可以顯示某些情況,這些snippits彼此之間的行為不同嗎?

我只能想到一些差異 - 這里有一些不一定會造成傷害的例子(我認為)。 我省略了定義以保持簡潔

template <typename T> T inc(const T& t);
namespace G { using ::inc; }
template <> int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses explicit specialization

// --- against ---

template <typename T> T inc(const T& t);
namespace G { using ::inc; }
int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses template

這是因為通過名稱查找找不到特化,但是通過參數匹配,因此using聲明將自動考慮稍后引入的特化。

那么,你當然不能部分專門化功能模板。 然而,重載通過部分排序完成了非常類似的事情(現在使用不同的類型,以表達我的觀點)

template <typename T> void f(T t); // called for non-pointers
template <typename T> void f(T *t); // called for pointers.

int a;
void e() {
  f(a); // calls the non-pointer version
  f(&a); // calls the pointer version
}

使用函數模板顯式特化是不可能的。 另一個例子是涉及引用時,這會導致模板參數推導尋找所涉及類型的完全匹配(模基/派生類關系和常量):

template<typename T> void f(T const &);
template<> void f(int * const &);

template<typename T> void g(T const &);
void g(int * const &);

int a[5];
void e() {
  // calls the primary template, not the explicit specialization
  // because `T` is `int[5]`, not `int *`
  f(a);

  // calls the function, not the template, because the function is an
  // exact match too (pointer conversion isn't costly enough), and it's 
  // preferred. 
  g(a);
}

我建議你總是使用重載,因為它更豐富(允許部分特化允許的東西),此外你可以將函數放在你想要的任何命名空間中(雖然它不再嚴格超載)。 例如,您可以將swap重載放在您自己的命名空間中,並使其可由ADL調用,而不必在std:: namespace中專門化std::swap

無論你做什么, 永遠不要混合專業化和超載 ,這將是一個混亂,就像這篇文章指出的那樣。 該標准有一個可愛的段落

為函數模板,類模板,類模板的成員函數,類模板的靜態數據成員,類模板的成員類,類模板的成員類模板,類模板的成員函數模板,成員的成員函數放置顯式特化聲明類模板的模板,非模板類的成員模板的成員函數,類模板的成員類的成員函數模板等,類模板的部分特化聲明的放置,非模板類的成員類模板,成員類模板等的類模板可以根據顯式特化聲明的相對位置及其在翻譯單元中的實例化點的相對位置來影響程序是否格式良好,如上下文所述。 寫專業時,要注意它的位置; 或者使它編纂將是一種試圖點燃其自焚的試驗。

模板專業化比僅僅重載更通用。 你可以專注於類,而不僅僅是簡單的函數。 重載僅適用於函數。

更新:為了澄清更多AraK的評論,你真的在​​這里比較蘋果和橘子。 函數重載用於引入具有不同函數的能力,如果它們具有不同的簽名,則共享單個名稱。 模板特化用於定義特定類型參數的特定代碼段。 如果您沒有模板,則無法進行模板專業化。 如果刪除聲明通用模板的第一段代碼,如果嘗試使用模板特化,則會收到編譯時錯誤。

因此,模板特化的目標與函數重載完全不同。 它們恰好在你的例子中表現得相似,但它們根本不同。

如果提供重載,則聲明一個恰好具有相同名稱的獨立方法。 您沒有阻止模板與特定類型參數一起使用。 為了證明這一事實,請嘗試:

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

int inc(const int& t)
{
    return t + 42;
}

#include <iostream>
int main() {
   int x = 0;
   x = inc<int>(x);
   std::cout << "Template: " << x << std::endl; // prints 1.

   x = 0;
   x = inc(x);
   std::cout << "Overload: " << x << std::endl; // prints 42.
}

如您所見,在此示例中, int值有兩個不同的inc函數: inc(const int&)inc<int>(const int&) 如果您使用了模板特化,則無法使用int擴展通用模板。

一個這樣的例子:

#include <cstdio>

template <class T>
void foo(T )
{
    puts("T");
}

//template <>
void foo(int*)
{
    puts("int*");
}

template <class T>
void foo(T*)
{
    puts("T*");
}

int main()
{
    int* a;
    foo(a);
}

實際上,建議您對函數使用非模板重載,並為類保留特殊化。 為什么不專業化功能模板對它進行了更詳細的討論

AFAIK沒有功能差異。 我可以添加的是,如果您同時具有模板函數特化和定義的普通函數,則不存在過載歧義,因為普通函數是有利的。

只是詳細說明litb在他的回答中提到的第一點。 只有在重載決策實際選擇了主模板后才會檢查專精。 結果可能導致一些意外,其中函數被重載並具有明確的特化:

template <typename T> void foo (T);  // Primary #1
template <> void foo<int*> (int*);   // Specialization of #1

template <typename T> void foo (T*); // Primary #2

void bar (int * i)
{
  foo(i);
}

選擇要調用的函數時,將執行以下步驟:

  1. 名稱查找查找兩個主模板。
  2. 每個模板都是專用的,重載決策嘗試根據參數和參數之間的轉換選擇最佳函數。
  3. 在這種情況下,轉換質量沒有差異。
  4. 然后使用部分排序規則來選擇最專業的模板。 在這種情況下,這是第二個parimary“foo(T *)”。

只有在這些步驟之后,當選擇了最佳函數時,才會考慮所選函數的顯式特化。 (在這種情況下,主#2沒有,所以沒有考慮)。

在這里調用上述顯式特化的唯一方法是在調用中實際使用顯式模板參數:

void bar (int * i)
{
  foo<int*> (i);  // expliit template argument forces use of primary #1
}

一個好的經驗法則是盡量避免具有明顯專門化的重載,因為生成的規則非常復雜。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM