1. 程式人生 > 其它 >模板第二版 ch01

模板第二版 ch01

這是學習模板第二版的筆記和部分翻譯。

函式模板

本章介紹函式模板,其是被引數化,這樣它們可以表示一簇函式。

1.1 初識函式模板

函式模板提供函式行為,其可以被不同的型別所呼叫。換句話說,一個函式模板表示一簇函式。該表示看起來和普通函式很多相同,除了函式的一些元素尚未確定外:這些元素被引數化。下面可以看看一個簡單的樣例。

1.1.1定義模板

下面是返回兩個值的最大值的函式模板:

template <typename T>
T max (T a, T  b)
{
    return b < a ? a : b;
}

該模板定義了這樣一類函式,其返回兩個值的較大值,這兩個值是作為函式引數a和b傳遞的。引數的型別是左邊的模板引數T。正如上面樣例所示,模板引數使用下面的語法形式宣告的:

template <comma-separated-list-of-parameters>

在樣例中,引數列表是typename T。注意到<>被用作括號。關鍵字typename引入型別引數。這也是在C++模板中使用最多的種類,當然其他引數也是可行的,這將會在後續進行討論。
這裡,型別引數為T,當然你也可以使用任何識別符號作為引數名稱,但一般用T是習慣。型別引數表示任意型別,其會在呼叫者呼叫該函式時由呼叫者所決定。你可以使用任意型別(基本型別、類等)只要其提供模板所使用的運算。在樣例中,型別T必須提供運算<,因為a和b使用該運算子進行比較。或許從max()定義中的並不明顯的是型別T的值必須是可拷貝的,為了能夠返回。
出於歷史原因,你也可以使用關鍵字class,而不是typename來定義型別引數。關鍵字typename是在C++98標準稍後出來的。在這之前,class只是引入型別引數,這種方式就被保留下來。因此可以等價寫成下面的形式:

template <class T>
T max(T a, T b)
{
    return b < a ? a : b;
}

在語義上,和前面的定義沒有任何區別。所以即使這裡使用了class,任何型別都可以用於模板引數。然而,因為使用class可能會有誤導,你應該傾向於使用typename在這種上下文環境中。注意,這裡不是類型別宣告,這裡無法使用關鍵字struct來取代typename,當宣告型別引數時。

1.1.2使用模板

下面程式顯示了怎麼使用max()函式模板:

#include "max1.hpp"
#include <iostream>
#include <string>
int main()
{
int i = 42;
std::cout << "max(7,i):
" << ::max(7,i) << ’\n’;
double f1 = 3.4; double f2 = -6.7;
std::cout << "max(f1,f2):
" << ::max(f1,f2) << ’\n’;
std::string s1 = "mathematics"; std::string s2 = "math";
std::cout << "max(s1,s2):
" << ::max(s1,s2) << ’\n’;
}

在程式內,max()被呼叫三次:一次是兩個ints,一次是兩個double,最後一次是std::string。每次,都會計算最大值,作為程式的輸出如下:
max(7,i): 42
max(f1,f2): 3.4
max(s1,s2): mathematics
注意到每次呼叫max()模板都是帶著::限定符,這是為了保證在全域性名稱空間中能夠找到我們的max模板,在STL中也有一個std::max()模板,在某些環境下可能會被呼叫或者導致二義性。
模板並不會編譯成能夠處理任何型別的單個實體。而是,對於每個型別都會從模板中生成不同的實體。因此,max()都會為三種類型進行編譯。比如,max()的第一次呼叫 :

int i = 42;
... max(7,i) ...

該函式模板使用int作為模板引數T,這樣,其具有下面程式碼的語義:

int max (int a, int b)
{
return b < a ? a : b;
}

由具體型別來取代模板引數的過程叫做例項化。其會導致一個模板的例項。
注意僅僅函式模板的使用就可以出發這樣的例項化過程。這裡無需程式設計師來單獨請求例項化。
類似地,其他對max()的呼叫會例項化max模板為double和std::string,就好像它們已經宣告和單獨實現:

double max (double, double);
std::string max (std::string, std::string);

注意到void也是有效的模板引數,其提供的結果程式碼是有效的。比如:

template<typename T>
T foo(T*)
{
}
void* vp = nullptr;
foo(vp);             // OK: deduces void foo(void*)

1.1.3 兩階段轉換

對於一個不支援模板內部的所有的運算子的型別的例項化嘗試將會導致編譯期錯誤。比如:

std::complex<float> c1, c2;
...
::max(c1,c2);
// doesn’t provide operator <
// ERROR at compile time

這樣,模板在兩個階段被編譯:

  1. 不是在定義時例項化,模板程式碼本身在忽略模板引數的情況下檢查正確性,這包括:
  • 發現語法錯誤,比如缺少分號;
  • 使用並不依賴於模板引數的未知名稱(型別名稱、函式名稱...),被發現;
  • 檢查並不依賴於模板引數的靜態斷言;
  1. 在例項化時,會再次檢查模板程式碼來確保所有程式碼都是有效的。即,現在尤其特殊,再次檢查所有依賴於模板引數的部分;
    (總結:第一次檢查就是定義模板程式碼時,假定模板引數相關都是對的,檢查那些非模板引數相關的問題, 等到了模板例項化,會再次將模板引數相關的問題進行檢驗)。
    樣例:
template<typename T>
void foo(T t)
{
undeclared();
// first-phase compile-time error if undeclared() unknown
undeclared(t);
// second-phase compile-time error if undeclared(T) unknown
static_assert(sizeof(int) > 10,
// always fails if sizeof(int)<=10
"int too small");
static_assert(sizeof(T) > 10,
//fails if instantiated for T with size <=10
"T too small");
}

這種檢查的名稱叫做兩階段查詢,將會在本書的14章進行討論。
注意到,一些編譯器並不會在第一階段執行所有的檢查,所以你可能會在例項化階段才能看到一般的錯誤。

編譯和連結

兩階段轉換會在實際中處理模板時導致一個重要的問題:當函式模板被以一種方式所使用,其會觸發其例項化,編譯器將(在一些點上)需要看見模板的定義。這會打破對於一般函式的編譯和連結區別, 當函式的宣告足以應付編譯時的使用。處理該問題的方法在第9章討論。對於當前,讓我們採取最簡單的辦法:在標頭檔案中實現每個模板。

1.2 模板引數推導

當我們用一些引數呼叫一個函式模板,比如max(),模板引數由我們所傳遞的引數決定。如果我們傳遞兩個int作為引數型別,那麼C++編譯器推導處T必須是int。
然而, T可能只是型別的一部分。比如,如果我們使用常量引用來宣告max():

template<typename T>
T max (T const& a, T const& b)
{
return b < a ? a : b;
}

傳遞int,T再次被推導為int,因為函式引數匹配成int const&。在型別推導期間,需要注意到自動型別轉換有一些限制:

  • 當通過引用宣告呼叫引數時,即使是普通的轉換都不會應用型別推導。用相同模板引數T宣告的兩個引數必須準確匹配。
  • 當通過傳值宣告呼叫引數時,只有普通轉換支援decay:帶有const和volatile的限定符會被忽略,引用會轉換為引用型別,原始的陣列或函式轉換為對應的指標型別。對於帶有相同模板引數T的兩個引數的decayed型別必須匹配。
template<typename T>
T max (T a, T b);
...
int const c = 42;
max(i, c);
// OK: T is deduced as int
max(c, c);
// OK: T is deduced as int
int& ir = i;
max(i, ir);
// OK: T is deduced as int
int arr[4];
foo(&i, arr);
// OK: T is deduced as int*

下面是錯誤的:

max(4, 7.2);
std::string s;
foo("hello", s);
// ERROR: T can be deduced as int or double
//ERROR: T can be deduced as char const[6] or std::string

這裡有三種方式來處理這種錯誤:

  1. 強制轉換為相匹配的型別:
max(static_cast<double>(4), 7.2);
  1. 顯式指定T的型別,從而阻止編譯器進行型別推導:
max<double>(4, 7. 2);   //OK
  1. 指定引數可能有不同的型別, 就是允許模板引數使用多個型別;

對於預設引數的型別推導

注意到對於預設呼叫引數,型別推導將不會起作用, 比如:

template <typename T>
void f(T = "");

f(1);     // OK: deduced T to be int, so that it calls f<int>(1)
f();      // ERROR: cannot deduce T

為了支援這種情況,你必須為模板引數宣告一個預設引數,這將在1.4節進行討論。

template<typename T = std::string>
void f(T = "");
...
f();       // OK

1.3 多個模板引數

正如我們目前所看到的那樣,函式模板有兩個不同的引數集合:

  1. 模板引數,其是在函式模板名稱之前的尖括號中宣告,比如:
template <typename T>   // T is template parameter;
  1. 呼叫引數,其是在函式模板名稱後的parentheses中宣告的:
T max (T a, T b)    // a and  b are call parameters;

你可能有許多模板引數。比如,你可以定義max()模板來給兩個呼叫引數不同的型別:

template<typename T1, typename T2>
T1 max (T1 a, T2 b)
{
return b < a ? a : b; }
...
auto m = ::max(4, 7.2);             // OK, but type of first argument defines return type

其可能顯式能夠傳遞不同的型別給max()模板,但是,正如這個模板所示,其會產生一個問題。如果你使用一個引數型別作為返回型別,其他引數型別的引數可能會轉換成這個型別,不管呼叫者的意圖。這樣,返回型別會取決於呼叫引數的熟悉怒。比如66.66和42的最大值京會是66.66,而44和66.66將會返回int 66。
C++提供不同的方法來解決該問題:

  • 引入第三個模板引數作為返回型別;
  • 讓編譯器找出返回型別;
  • 宣告返回型別變為兩個引數型別的"公共型別";
    接下來就是討論這個選項。

1.3.1模板引數用於返回型別

我們之前的討論顯式模板引數推導使得我們可以呼叫函式模板和普通函式一樣的語法來呼叫。我們並沒有顯式指定對應的模板引數。
然而,我們也提到我們可以顯式指定模板引數的型別:

template<typename T>
T max (T a, T b);
...
::max<double>(4, 7.2);          // instantiate T as double

當模板引數和呼叫引數沒有關聯和模板引數無法被決定時這種情況下,你必須顯式指定呼叫的模板引數。比如,你可以引入第三個模板引數型別來定義函式模板的返回型別:

template<typename T1, typename T2, typename RT>
RT max (T1 a, T2 b);

然而,模板引數推導並不會將返回型別考慮在內,RT並不會出現在函式呼叫引數的型別中,因此,RT無法被推導。
作為一個結果,你必須顯式指定模板引數列表,比如:

template<typename T1, typename T2, typename RT>
RT max (T1 a, T2 b);
...
::max<int,double,double>(4, 7.2);       // OK, but tedious

到目前位置,我們已經查看了函式模板引數要麼所有,要麼都沒有顯式提及。另一個方法是顯式指定第一個引數並讓推導過程來派生餘下的型別。一般而言,你必須指定所有的引數型別直到最後一個無法被隱式的引數型別。這樣,如果你在我們的樣例中變更了引數順序,呼叫這隻需要指定返回型別:

template<typename RT, typename T1, typename T2>
RT max (T1 a, T2 b);
...
::max<double>(4, 7.2)
//OK: return type is double, T1 and T2 are deduced

在這個樣例中,對max的呼叫顯式設定了RT為double,但是引數T1和T2都通過引數推導為int和double。
注意到這些對max()的修改版本並不會導致有顯著優勢。對於一引數的版本,你可以已經指定引數和返回型別,如果傳遞兩個不同的型別引數。這樣,保持簡單是一個好主意,並使用max()的一引數版本。

1.3.2推導返回型別

如果返回型別取決於模板引數,最簡單和最好的辦法是推導返回型別讓編譯器來找。自從C++14,其可以通過簡單而不用宣告任何返回型別(你必須還需要用auto來宣告返回型別):

template <typename T1, typename T2>
auto max(T1 a, T2 b)
{
    return b < a ? a : b;
}

事實上,對於返回型別使用auto而不用對應trailing返回型別(其會在尾部引入一個->)表明實際的返回型別必須從函式體中的返回語句來推導。當然,從函式體中推導返回型別必須是可行的。因此,該程式碼是可行的,並匹配多個返回語句。
在C++14之前,其只能通過或多或少讓函式的實現是宣告的一部分讓編譯器決定返回型別。在C++11我們可以從trailing返回型別語法中受益,允許我們使用呼叫引數。即,我們可以宣告返回型別,這些返回型別是從運算子?:中派生產生的。

template<typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(b<a?a:b)
{
return b < a ? a : b;
}

這裡,結果型別是由運算子?:的規則所決定的,一般會產生一個非直觀的期望結果。

template<typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(b<a?a:b);

是一個宣告,這樣編譯器使用運算子?:的規則呼叫給引數a和b來在編譯期找出max()的返回型別。實現並沒有必要匹配。實際上,在宣告中使用true作為運算子 ? :的條件依然足夠:

template<typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(true?a:b);

然而,在許多情況下這種定義有重大缺陷:其可能在返回型別是一個引用型別時發聲,因為在某些情況下條件T必須是一個引用。出於這個原因,你必須返回從T decayed的型別,看起來如下:

#include <type_traits>
template<typename T1, typename T2>
auto max (T1 a, T2 b) -> typename std::decay<decltype(true?a:b)>::type
{ return b < a ? a : b;
}

這裡使用了型別特性std::decay<>,其會返回在成員type中返回結果型別。其是由在<type_trait>的標準庫中定義的。因為成員type是一個型別,你必須用typename來限定表示式來訪問它。
注意到型別auto的初始化總是decays。當返回型別也是auto時,也會作用到返回型別。auto作為返回型別表現如同下面的程式碼一樣,這裡a是一個由i, int decayed的型別所宣告:

int i = 42;
int const& ir = i;    // ir refers to
i auto a = ir;      // a is declared as new object of type int
//PS:這句話,其實應該有錯誤,auto前面的i應該是筆誤,否則無法通過gcc的編譯;

1.3.3 返回型別作為公共型別

自從C++11,C++標準庫提供了一種方法來指定選擇更通用的類。std::common_type<>::type產生作為模板引數的兩個或多個不同型別的"公共型別"。比如:

#include <type_traits>
template<typename T1, typename T2>
std::common_type_t<T1,T2> max (T1 a, T2 b)
{
return b < a ? a : b;
}

再次,std::common_type是一個型別特性,定義在<type_traits>,其會生成一個結構體,其中有type成員是表示結果型別的。這樣,其核心使用如下:

typename std::common_type<T1,T2>::type
//since C++11

然而,自從C++14開始,你可以簡化特性的使用,在特性名稱後面加上_t,並跳過typename和::type,這樣上面的返回型別定義簡化為:

std::common_type_t<T1, T2>     //equivalent since C++14;

std::common_type<>的實現使用了一些模板程式設計的tricky,會在26.5中討論。其會更覺運算子 ?:的語言規則或這特定型別的特化來選擇返回型別。這樣,::max(4, 7.2)和::max(7.2, 4)產生相同的結果double型別7.2。注意到std::common_type<>也是decays。

1.4預設模板引數

你也可以為模板引數定義預設值。這些值叫做預設模板引數並可以和任意種類的模板一起使用。其甚至可以引用到之前模板引數。
比如,如果你想要結合定義返回型別的方法和有多個引數型別的能力,你可以引入模板引數RT用於返回型別,預設作為兩個引數的公共型別。再次,我們有多個選項:

  1. 直接使用運算子?:。然而,因為我們不得不使用運算子?:在呼叫引數a和b被宣告之前,我們可以只使用其型別:
basics/maxdefault1.hpp
#include <type_traits>
template<typename T1, typename T2,
typename RT = std::decay_t<decltype(true ? T1() : T2())>>
RT max (T1 a, T2 b)
{
return b < a ? a : b;
}

注意到std::decay_t<>的再次使用來確保沒有返回引用。
也注意到該實現需要我們能夠給傳遞的型別呼叫預設建構函式。這裡有另一種解決方法,使用std::declval,然而,其會使得宣告變得更加複雜。
2. 我們也可使用std::common_type<>型別特性來為返回型別指定預設值:

basics/maxdefault3.hpp
#include <type_traits>
template<typename T1, typename T2,
typename RT = std::common_type_t<T1,T2>>
RT max (T1 a, T2 b)
{
return b < a ? a : b;
}

再次,注意到std::common_type<> decays這樣返回值就不會變成一個引用。在所有情況下,作為一個呼叫者,你現在可以給返回型別使用預設值:

auto a = ::max(4, 7.2);

或者在所有其他引數型別顯式後指定返回型別:

auto b = ::max<double,int,long double>(7.2, 4);

然而,再次我們遇到困難,我們必須指定三個型別從而能夠指定返回型別。如果我們需要返回型別作為第一個模板引數的能力,剩餘的將會從引數型別中推導。原則上,對於前導函式模板引數有預設引數即使引數沒有下面的預設引數:

template<typename RT = long, typename T1, typename T2>
RT max (T1 a, T2 b)
{
return b < a ? a : b;
}

在這個定義下,比如,你可以這麼呼叫:

int i; long l;
...
max(i, l);   // returns long (default argument of template parameter for return   type)
max<int>(4, 42);    // returns int as explicitly requested

如果這裡對於模板引數是天然的預設,那麼該方法才是有意義的。這裡我們需要,給模板引數的預設引數依賴與之前模板引數。原則上,這是可能的,正如在26.5中討論,但是該技術依賴於型別特性和複雜的定義。
基於上面的原因,最好的和最簡單的方式是讓編譯器來推導,正如在1.3.2中所述一樣。

1.5過載函式模板

和普通函式一樣,函式模板可以被過載。即是,你可以用相同的函式名稱,卻有多個不同的函式定義,這樣函式名稱可以在函式呼叫中被使用,C++編譯器必須決定呼叫的是哪一個候選者。該決定的規則可能會變得相當複雜,即使沒有模板的情況下。在本節我們討論當涉及到模板時的過載。如果你不太熟悉沒有模板時的過載基本規則,可以參考附錄C,這裡我們提供了過載方案規則一個相對合理的細節。
下面介紹函式模板的過載:

basics/max2.cpp
// maximum of two int values:
int max (int a,
int b)
{
return b < a ? a : b;
}
// maximum of two values of any type:
template<typename T>
T max (T a, T b)
{
return b < a ? a : b;
}

int main()
{
::max(7, 42);   //calls the nontemplate for two ints
::max(7.0, 42.0);  // calls max<double> (by argument deduction)
::max(’a’, ’b’);   // calls max<char> (by argument deduction)
::max<>(7, 42);   //calls  max<int> (by argument deduction)
::max<double>(7, 42);  // calls max<double> (no argument deduction)
::max(’a’, 42.7);  // calls the nontemplate for two ints
}

正如這個例子所示,非模板函式可以與有相同名稱的函式模板並可以例項化為相同的型別共存。所有其他因素都是相同的,過載解析過程相對於從模板中生成的函式會優先非模板函式。第一個呼叫就是遵循這個規則:

::max(7, 42);       // both int values match the nontemplate function perfectly

然而,當從模板中可以生成一個更加匹配的函式時,就會選擇模板。這由max第二個例子和第三個呼叫就可以證實:

::max(7.0, 42.0);     // calls the max<double> (by argument deduction)
::max(’a’, ’b’);        //calls the max<char> (by argument deduction)

這裡,模板是一個更好的匹配,因為沒有要求從double或char到int的轉換。
也可以顯式的指定空的模板引數列表。該語法表示唯一的模板可以解析呼叫,但是所有的模板引數都可以從呼叫引數中推導得到:

::max<>(7, 42);         // calls max<int> (by argument deduction)

一個有趣的例子可過載max模板,能夠顯式只指定返回型別:

//basics/maxdefault4.hpp
template<typename T1, typename T2>
auto max (T1 a, T2 b)
{
return b < a ? a : b;
}
template<typename RT, typename T1, typename T2>
RT max (T1 a, T2 b)
{
return b < a ? a : b;
}

現在,我們可以呼叫max,比如,如下:

auto a = ::max(4, 7.2);    // uses first template
auto b = ::max<long double>(7.2, 4);        // uses second template

然而,當呼叫:

auto c = ::max<int>(4, 7.2);
// ERROR: both function templates match

兩個模板都可以匹配,這會導致過載方案過程無法選擇出更好的,會導致二義性錯誤。這樣,當過載函式模板時,你應該確保它們只能後其中一個匹配上。
對於為指標和普通C字串的過載max模板的有用例子是:

//basics/max3val.cpp
#include <cstring>
#include <string>

// maximum of two values of any type:
template<typename T>
T max (T a, T b)
{
    return b < a ? a : b;
}
// maximum of two pointers:
template<typename T>
T* max (T* a, T* b)
{
    return  *b < *a ? a : b;
}
// maximum of two C-strings:
char const* max (char   const* a,   char const* b)
{
    return  std::strcmp(b,a) < 0   ? a : b;
}

int main ()
{
    int a = 7;
    int b = 42;
    auto m1 = ::max(a,b); // max() for two values of type int
    std::string s1 ="hey"; 
    std::string s2 = "you"; 
    auto m2 = ::max(s1,s2); // max() for two values of type std::string
    int* p1 = &b;
    int* p2 = &a;
    auto m3 = ::max(p1,p2); // max() for two pointers
    char const* x = "hello"; 
    char const* y = "world"; 
    auto m4 = ::max(x,y);   // max() for two C-strings
}

注意到max()所有過載中我們通過值傳遞。一般而言,在過載函式模板時除了必須改變外,最好是不要修改。你應該限制你的變更引數的數目或顯式的指定模板引數。否則,就會產生意料之外的影響。比如,如果你實現max()模板通過引用來傳遞,並在兩個C-string的過載版本中值傳遞,你將無法使用三個引數的版本來計算三個C-string的最大值:

#include <cstring>
// maximum of two values of any type (call-by-reference)
template<typename T> 
T const& max (T const& a, T const& b)
{
        return b < a ? a : b;
}
// maximum of two C-strings (call-by-value)
char const* max (char const* a, char const* b)
{
    return std::strcmp(b,a) < 0 ? a : b;
}
// maximum of three values of any type (call-by-reference)
template<typename T>
T const& max (T const& a, T const& b, T const& c)
{
    return max (max(a,b), c);
// error if max(a,b) uses call-by-value
}
int main ()
{
auto m1 = ::max(7, 42, 68);
// OK
char const* s1 = "frederic";
char const* s2 = "anica";
char const* s3 = "lucas";
auto m2 = ::max(s1, s2, s3);
//run-time ERROR
}

問題在於,如果你為三個C-string呼叫max(),該語句:

return max(max(a, b), c);

將會變得執行時錯誤,因為C-string,max(a, b)會建立一個新的、臨時的區域性變數,其會在返回時返回引用,但是該臨時變數值在返回語句返回時就已經消亡,使得main()中是一個空引用。不幸的是,該錯誤是如此脆弱,並不會在所有情況中出現。
相反第,注意到,在main中的第一個max呼叫並沒有遇到這樣的問題。會為(7.42, 68)引數建立臨時變數,但是這些變數在main中被建立,這裡它們會持續到該語句完成。
這只是一個程式碼示例,由於詳細的過載解析規則。此外,請確保在函式呼叫之前宣告函式的所有過載版本。這是因為在一個對應函式被呼叫時,不是所有的過載函式都是可見的,可能會導致問題。比如,在max的三引數版本中,就沒有看見max的特殊兩ints引數版本,導致三引數版本使用兩引數模板:

#include <iostream>
// maximum of two values of any type:
template<typename T>
T max (T a, T b)
{
    std::cout << "max<T>() \n";
    return b < a ? a : b;
}
// maximum of three values of any type:
template<typename T>
T max (T a, T b, T c)
{
return max (max(a,b), c);   // uses the template version even for ints
//because the following declaration comes
// too late:
}
// maximum of two int values:
int max (int a, int b)
{
    std::cout << "max(int,int) \n";
    return b < a ? a : b;
}
int main()
{
    ::max(47,11,33);   // OOPS: uses max<T>() instead of max(int,int)
}

1.6 但是,我們不應該... ?

或許,即使這些簡單的函式模板樣例可能產生進一步的問題。三個問題可能比較普通,我們這裡簡單的討論一下。

1.6.1 傳值還是傳引用 ?

你可能好奇,為什麼一般宣告函式通過值傳遞引數而不是使用引用。一般來說,引用傳遞是推薦給型別,而不是簡單的型別(比如基本型別或者std::string)view),因為並沒有建立多餘的拷貝。
然而,由於一系列原因,值傳遞一般來說更合適:

  • 語法簡單;
  • 編譯器優化更好;
  • 移動語義經常使得拷貝更加便宜;
  • 有時這裡根本沒有拷貝或者移動;
    此外,對於模板,可以扮演特定角色:
  • 一個模板可能既用於簡單型別及複雜型別,所以選擇給複雜型別的方式可能對於簡單型別會適得其反;
  • 作為呼叫者,你通常可以決定使用引用傳參,使用std::ref()和std::cref();
  • 儘管傳遞字元常量或者原生陣列會成為一個問題,將其通過引用傳遞可能會導致更大的問題。所有的這一切都在第7章進行討論。當前我們儘可能考慮用值傳遞除非只能通過引用傳遞。

1.6.2 為什麼不內聯?

一般而言,函式模板並不會用內聯進行宣告。不像一般函式,我們可以在標頭檔案中定義非行內函數模板,在多個翻譯單元中包括該檔案。
該規則的唯一例外是特定型別的完全特化,這樣結果程式碼不再是泛型。
從嚴格的語言定義角度來看,inline就只意味著函式的定義可以多次出現在一個程式中。然而,其也意味著對編譯器的提示,對該函式的呼叫應該是"擴充套件內聯的":這麼做可以在某些確定情況下生成更高效的程式碼。如今,編譯器更擅長於在不使用內聯關鍵字的暗示下決定此操作。然而,編譯器仍然要考慮在該決定中存在內聯。

1.6.3 為什麼不constexpr?

自從C++11,你可以使用constexpr來提供使用程式碼來在編譯期計算一些值的能力。對於很多模板而言,這個很重要。
比如,為了在編譯期使用max()函式,你不得不如下宣告它:

basics/maxconstexpr.hpp
template<typename T1, typename T2>
constexpr auto max (T1 a, T2 b)
{
    return b < a ? a : b;
}

伴隨這個,你可以在編譯期上下文中使用max()函式模板,比如在宣告原生陣列大小時:

int a[::max(sizeof(char), 1000u)];

或者std::array<>的大小:

std::array<std::string, ::max(sizeof(char), 1000u)> arr;

注意到我們傳遞1000作為無符號整形來避免在模板內部出現有符號和無符號比較的的比較警告。
後面會討論這個,當前只聚焦與基礎部分,選擇跳過constexpr。