
template 是 c++ 相當重要的組成部分,堪稱(chēng) c++語(yǔ)言的一大利器。在大大小小的 c++ 程序中,模板無(wú)處不在。c++ templates 作為模板學(xué)習的經(jīng)典書(shū)籍,歷來(lái)被無(wú)數 c++學(xué)習者所推崇。第二版書(shū)籍覆蓋了 c++ 11 14 和 17 標準,值得程序猿們精讀學(xué)習,特此整理學(xué)習筆記,將每一部分自認為較為重要的部分逐條陳列,并對少數錯誤代碼進(jìn)行修改
1.模板實(shí)例化時(shí),模板實(shí)參必須支持模板中類(lèi)型對應的所有運算符操作。
template <typename T>
T max(const T &a, const T &b) {
return a > b? a : b;
}
class NdGreater {
};
int main() {
NdGreater n1, n2;
::max(n1, n2); // 不支持 > 編譯報錯
}
2.模板編譯時(shí)會(huì )進(jìn)行兩階段檢查
a.模板定義時(shí),進(jìn)行和類(lèi)型參數無(wú)關(guān)的檢查,如未定義的符號等。
b.模板實(shí)例化時(shí),進(jìn)行類(lèi)型參數相關(guān)的檢查。
template<typename T>
void foo(T t) {
undeclared(); // 如果 undeclared()未定義,第一階段就會(huì )報錯,因為與模板參數無(wú)關(guān)
static_assert(sizeof(T) > 10, 'T too small'); //與模板參數有關(guān),只會(huì )在第二階段報錯
}
3.根據兩階段檢查,模板在實(shí)例化時(shí)要看到完整定義,最簡(jiǎn)單的方法是將實(shí)現放在頭文件中。
1.函數模板的模板參數可以通過(guò)傳遞的函數參數進(jìn)行推斷。
2.函數推斷時(shí)會(huì )用到參數類(lèi)型轉換,規則如下:
a.如果函數參數是按引用傳遞的,任何類(lèi)型轉換都不被允許。(此處有疑問(wèn),const 轉換還是可以的)
b.如果函數參數是按值傳遞的,可以進(jìn)行退化(decay)轉換:const(指針或者引用只有頂層 const 可以被忽略) 和 volatile 被忽略;引用變?yōu)榉且?;數組和函數變?yōu)閷羔橆?lèi)型。
template <typename T>
void RefFunc(const T &a, const T &b){};
template <typename T>
void NoRefFunc(T a, T b){};
int main() {
int *const ic = nullptr;
const int *ci = nullptr;
int *p = nullptr;
RefFunc(p, ic); // ok 頂層const可以被忽略 T 為 int *
RefFunc(p, ci); // error 底層const不可以忽略
NoRefFunc(p, ci); // error 底層const不可以忽略
int i = 0;
int &ri = i;
NoRefFunc(i, ri); // ok ri從int &轉換為int
int arr[4];
NoRefFunc(p, arr); // ok arr 被推斷為int *
NoRefFunc(4, 5.0); // error T 可以推斷為int或double
}
3.上文的最后一句調用,類(lèi)型推斷具有二義性,無(wú)法正確實(shí)例化??梢酝ㄟ^(guò)以下方式解決
a.類(lèi)型轉換:
b.顯式指定模板實(shí)參:
NoRefFunc(static_cast<double>(4), 5.0); // ok 類(lèi)型轉換
NoRefFunc<int>(4, 5.0); // 顯式指定
4.函數模板無(wú)法通過(guò)默認參數推斷模板參數。如果函數模板只有一個(gè)函數參數,且函數參數提供了默認值的情況,應該為模板類(lèi)型參數 T 也提供和函數參數默認值匹配的默認類(lèi)型。
template <typename T>
void Default(T t = 0){};
Default(); // error 無(wú)法推斷為int
template <typename T = int>
void Default(T t = 0){};
Default(); // ok 默認類(lèi)型為int
1.當函數返回類(lèi)型不能或不便由函數參數類(lèi)型直接推斷時(shí),可以在函數模版中新增模板參賽指定返回類(lèi)型。
2.c++11 之后,可以通過(guò) auto + decltype +尾后返回類(lèi)型 推斷函數模板返回類(lèi)型。當函數參數為引用類(lèi)型時(shí),返回類(lèi)型應該為非引用。而decltype 會(huì )保留引用,因此還需通過(guò) decay 進(jìn)行類(lèi)型退化。
3.c++14 之后,可以通過(guò) auto 直接推斷函數模板返回類(lèi)型,前提是函數內部的多個(gè)返回語(yǔ)句推斷出的返回類(lèi)型要一致。auto 會(huì )自動(dòng)對類(lèi)型進(jìn)行 decay。
4.c++11 之后,可以通過(guò) common_type 返回多個(gè)模版類(lèi)型參賽的公共類(lèi)型,common_type 返回的類(lèi)型也是 decay 的。
#include<type_traits>
// 單獨通過(guò)RT指定返回類(lèi)型
template <typename RT, typename T1, typename T2>
RT max1(const T1& a, const T2& b) { return a > b ? a : b; }
// auto c++11支持 通過(guò)decay 進(jìn)行類(lèi)型退化 typename 用于聲明嵌套從屬名稱(chēng) type 為類(lèi)型而不是成員
template <typename T1, typename T2>
auto max2(const T1& a, const T2& b) -> typename std::decay<decltype(a > b ? a : b)>::type { return a > b ? a : b; }
// auto c++14支持
template <typename T1, typename T2>
auto max3(const T1& a, const T2& b) { return a > b ? a : b; }
// common_type c++11支持 max4(5, 7.3) max4(7.4, 5) 的返回類(lèi)型均被推斷為double
template <typename T1, typename T2>
typename std::common_type<T1, T2>::type max4(const T1& a, const T2& b) { return a > b ? a : b; }
1.可以給模板參數指定默認值。
// 默認模板參賽 因為RT需要T1 T2推斷,所以放在最后
template <typename T1, typename T2, typename RT = typename std::common_type<T1, T2>::type>
RT max5(const T1& a, const T2& b) { return a > b ? a : b; }
1.一個(gè)非模板函數可以和同名的函數模板共存,并且函數模板可實(shí)例化為和非模板函數具有相同類(lèi)型參數的函數。函數調用時(shí),若匹配度相同,將優(yōu)先調用非模板函數。但若顯式指定模板列表,則優(yōu)先調用函數模板。
2.函數模板不可以進(jìn)行類(lèi)型自動(dòng)轉換,非模板函數可以。
#pragma once
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
template <typename RT, typename T1, typename T2>
RT max(T1 a, T2 b) {
return a > b ? a : b;
}
int max(int a, int b) {
return a > b ? a : b;
}
int main() {
::max(6, 8); // 調用非模板函數
::max<>(6, 8); // 調用函數模板 max<int>
::max('a', 'b'); // 調用函數模板 max<char>
::max(4, 4.0); // 通過(guò)類(lèi)型轉換調用非模板函數
::max<double>(4, 4.0); //指定了返回類(lèi)型 調用max<double,int,double>
}
3.調用函數模板時(shí),必須保證函數模板已經(jīng)定義。
int max(int a, int b) {
return a > b ? a : b;
}
template <typename T>
T max(T a, T b, T c) {
return max(max(a,b),c); //T為int時(shí),并不會(huì )調用max<int> 而是調用非模板函數
}
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
max(1, 2, 3); // 最終調用非模板函數比較
max('sjx', 'wyl', 'shh'); // error 找不到二元的max<const char *>
1.類(lèi)模板不可以定義在函數作用域或者塊作用域內部,通常定義在 global/namespace/類(lèi)作用域。
#include<vector>
#include<iostream>
template <typename T>
class Stack
{
public:
void push(const T& value);
void pop();
T top();
int size() const { elem_.size(); };
bool empty() const { return elem_.empty(); };
void print(std::ostream & out) const;
protected:
std::vector<T> elem_;
};
template <typename T>
void Stack<T>::push(const T &value)
{
elem_.push_back(value);
}
template <typename T>
void Stack<T>::pop()
{
elem_.pop_back();
}
template <typename T>
T Stack<T>::top()
{
return elem_.back();
}
template <typename T>
void Stack<T>::print(std::ostream &out) const
{
for (auto e : elem_)
{
out << e << std::endl;
}
}
1.直到 c++17,使用類(lèi)模板都需要顯式指定模板參數。
2.類(lèi)模板的成員函數只有在調用的時(shí)候才會(huì )實(shí)例化。
1.類(lèi)模板實(shí)例化時(shí),模板實(shí)參只需要支持被實(shí)例化部分所有用到的操作。
int main()
{
// 只會(huì )實(shí)例化類(lèi)模板中的push 和 print函數
Stack<int> s;
s.push(3);
s.print(std::cout);
// Stack<int>未重載<<運算符,實(shí)例化print函數時(shí)失敗
Stack<Stack<int>> ss;
ss.push(s);
ss.print(std::cout);
return 0;
}
2.c++11 開(kāi)始,可以通過(guò) static_assert 和 type_traits 做一些簡(jiǎn)單的類(lèi)型檢查
template <typename T>
class C
{
static_assert(std::is_default_constructible<T>::value, 'class C requires default contructible');
};
1.可以對類(lèi)模板的一個(gè)參數進(jìn)行特化,類(lèi)模板特化的同時(shí)需要特化所有的成員函數,非特化的函數在特化后的模板中屬于未定義函數,無(wú)法使用。
// stringle類(lèi)型特化
template <>
class Stack<std::string>
{
public:
void push(const std::string& value);
/* 特化其他成員函數*/
};
1.類(lèi)模板特化時(shí),可以只特化部分參數,或者對參數進(jìn)行部分特化。
// 指針類(lèi)型特化
template <typename T>
class Stack<T *>
{
public:
void push(T *value);
void pop();
T* top();
int size() const { elem_.size(); };
bool empty() const { return elem_.empty(); };
protected:
std::vector<T *> elem_;
};
template <typename T>
void Stack<T*>::push(T *value)
{
elem_.push_back(value);
}
template <typename T>
void Stack<T*>::pop()
{
elem_.pop_back();
}
template <typename T>
T* Stack<T*>::top()
{
return elem_.back();
}
1.類(lèi)模板也可以指定默認模板參數。
template <typename T, typename COND = std::vector<T> >
class Stack
{
public:
void push(const T& value);
void pop();
T top();
int size() const { elem_.size(); };
bool empty() const { return elem_.empty(); };
protected:
COND elem_;
};
template <typename T, typename COND>
void Stack<T, COND>::push(const T &value)
{
printf('template 1\n');
elem_.push_back(value);
}
template <typename T, typename COND>
void Stack<T, COND>::pop()
{
elem_.pop_back();
}
template <typename T, typename COND>
T Stack<T, COND>::top()
{
return elem_.back();
}
1.為了便于使用,可以給類(lèi)模板定義別名。
typedef Stack<int> IntStack;
using DoubleStack = Stack<double>;
2.c++11 開(kāi)始可以定義別名模板,為一組類(lèi)型取一個(gè)方便的名字。
template <typename T>
using DequeStack = Stack<T, std::deque<T>>;
3.c++14 開(kāi)始,標準庫使用別名模板技術(shù),為所有返回一個(gè)類(lèi)型的 type_trait 定義了快捷的使用方式。
// stl庫定義
namespace std
{
template <typename T>
using add_const_t = typename add_const<T>::type;
}
typename add_const<T>::type; //c++ 11 使用
std::add_const_t<T>; //c++14使用
1.c++17 開(kāi)始,如果構造函數能夠推斷出所有模板參數的類(lèi)型,那么不需要指定參數類(lèi)型了。
template <typename T>
class Stack
{
public:
Stack() = default;
Stack(T e): elem_({e}){};
protected:
std::vector<T> elem_;
};
Stack intStack = 0; //通過(guò)構造函數推斷為int
2.類(lèi)型推導時(shí),構造函數參數應該按照值傳遞,而非按引用。引用傳遞會(huì )導致類(lèi)型推斷時(shí)無(wú)法進(jìn)行 decay 轉化。
Stack strStack = 'sjx';
//若構造函數參數為值傳遞,則T為const char *,引用傳遞時(shí)則為const char[4]
3.c++ 17 支持提供推斷指引來(lái)提供額外的推斷規則,推斷指引一般緊跟類(lèi)模板定義之后。
// 推斷指引,傳遞字符串常量時(shí)會(huì )被推斷為string
Stack<const char *> -> Stack<std::string>
1.聚合類(lèi):沒(méi)有顯式定義或繼承來(lái)的構造函數,沒(méi)有非 public 的非靜態(tài)成員,沒(méi)有虛函數,沒(méi)有 virtual,private ,protected 繼承。聚合類(lèi)也可以是模板。
template <typename T>
struct ValueWithComment
{
T val;
std::string comment;
};
ValueWithComment<int> vc;
vc.val = 42;
vc.comment = 'sjx';
1.模板參數不一定是類(lèi)型,可以是數值,如可以給 Stack 指定最大容量,避免使用過(guò)程元素增刪時(shí)的內存調整。
template <typename T, int MAXSIZE>
class Stack
{
public:
Stack():num_(0){};
void push(const T& value);
void pop();
T top();
int size() const { return num_; };
bool empty() const { return num_ == 0; };
protected:
T elem_[MAXSIZE];
int num_;
};
template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T &value)
{
printf('template 1\n');
assert(num_ < MAXSIZE);
elem_[num_++] = value;
}
template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
assert(num_ > 0);
--num_;
}
template <typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::top()
{
assert(num_ > 0);
return elem_[0];
}
1.函數模板也可以指定非類(lèi)型模板參數。
template<typename T, int VAL>
T addval(const T &num)
{
return num + VAL;
}
int main()
{
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<int>nums2(nums.size(),0);
std::transform(nums.begin(),nums.end(),nums2.begin(),addval<int,5>);
for(auto num : nums2)
{
printf('%d\n',num);
}
}
1.非類(lèi)型模板參數只能是整形常量(包含枚舉),指向 objects/functions/members 的指針,objects 或者 functions 的左值引用,或者是 std::nullptr_t(類(lèi)型是 nullptr),浮點(diǎn)數和類(lèi)對象不能作為非類(lèi)型模板參數。
2.當傳遞對象的指針或者引用作為模板參數時(shí),對象不能是字符串常量,臨時(shí)變量或者數據成員以及其它子對象。
3.對于非類(lèi)型模板參數是 const char*的情況,不同 c++版本有不同限制
a. C++11 中,字符串常量對象必須要有外部鏈接
b. C++14 中,字符串常量對象必須要有外部鏈接或內部鏈接
c. C++17 中, 無(wú)鏈接屬性也可
4.內部鏈接:如果一個(gè)名稱(chēng)對編譯單元(.cpp)來(lái)說(shuō)是局部的,在鏈接的時(shí)候其他的編譯單元無(wú)法鏈接到它且不會(huì )與其它編譯單元(.cpp)中的同樣的名稱(chēng)相沖突。例如 static 函數,inline 函數等。
6.非類(lèi)型模板參數的實(shí)參可以是任何編譯器表達式,不過(guò)如果在表達式中使用了 operator >,就必須將相應表達式放在括號里面,否則>會(huì )被作為模板參數列表末尾的>,從而截斷了參數列表。
#include<string>
template < double VAL > // error 浮點(diǎn)數不能作為非類(lèi)型模板參數
double process(double v)
{
return v * VAL;
}
template < std::string name > // error class對象不能作為非類(lèi)型模板參數
class MyClass {
};
template < const char * name >
class Test{
};
extern const char s01[] = 'sjx'; // 外部鏈接
const char s02[] = 'sjx'; // 內部鏈接
template<int I, bool B>
class C{};
int main()
{
Test<'sjx'> t0; // error before c++17
Test<s01> t1; // ok
Test<s02> t2; // since c++14
static const char s03[] = 'sjx'; // 無(wú)鏈接
Test<s03> t3; // since c++17
C<42, sizeof(int)> 4 > c; //error 第一個(gè)>被認為模板參數列表已經(jīng)結束
C<42, (sizeof(int)> 4) > c; // ok
}
1.從 C++17 開(kāi)始,可以不指定非類(lèi)型模板參數的具體類(lèi)型(代之以 auto),從而使其可以用于任意有效的非類(lèi)型模板參數的類(lèi)型。
template <typename T, auto MAXSIZE>
class Stack
{
public:
using size_type = decltype(MAXSIZE); // 根據MAXSIZE推斷類(lèi)型
public:
Stack():num_(0){};
void push(const T& value);
void pop();
T top();
size_type size() const { num_; };
bool empty() const { num_ == 0; };
protected:
T elem_[MAXSIZE];
size_type num_;
};
template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T &value)
{
printf('template 1\n');
assert(num_ < MAXSIZE);
elem_[num_++] = value;
}
template <typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
assert(num_ > 0);
--num_;
}
template <typename T, int MAXSIZE>
T Stack<T, MAXSIZE>::top()
{
assert(num_ > 0);
return elem_[0];
}
int main()
{
Stack<int,20u> s1; // size_type為unsigned int
Stack<double,40> s2; // size_type為int
}
1.c++11 開(kāi)始,模板可以接收一組數量可變的參數。
#include <iostream>
void print(){}; // 遞歸基
template<typename T, typename ...Types>
void print(T firstArg, Types ... args)
{
std::cout << firstArg << std::endl;
print(args...);
}
int main(){
print(7.5, 'hello', 10); // 調用三次模板函數后再調用普通函數
}
// 使用模板函數的遞歸基,最后只剩一個(gè)參數時(shí)會(huì )優(yōu)先使用本模板
template<typename T>
void print(T arg)
{
std::cout << arg << std::endl;
}
3.c++11 提供了sizeof...運算符來(lái)統計可變參數包中的參數數目
template<typename T, typename ...Types>
void print(T firstArg, Types ... args)
{
std::cout << sizeof...(Types) << std::endl;
std::cout << sizeof...(args) << std::endl;
std::cout << firstArg << std::endl;
print(args...);
}
4.函數模板實(shí)例化時(shí)會(huì )將可能調用的函數都實(shí)例化。
#include <iostream>
template<typename T, typename ...Types>
void print(T firstArg, Types ... args)
{
std::cout << firstArg << std::endl;
if(sizeof...(args) > 0)
print(args...); // error 缺少遞歸基。即使只在參數包數目>0時(shí)調用,args為0個(gè)參數時(shí)的print函數
}
int main(){
print(7.5, 'hello', 10); // 調用三次模板函數后再調用普通函數
}
1.C++17 提供了一種可以用來(lái)計算參數包(可以有初始值)中所有參數運算結果的二元運算符 ...

template<typename ...T>
auto sum(T ...s)
{
return (... + s); // ((s1+s2)+s3)...
}
template<typename ...T>
void PrintDouble(const T &... args)
{
print(args + args...); // 將args翻倍傳給print
}
template<typename ...T>
void addOne(const T & ...args)
{
print(args + 1 ...); // 1和...中間要有空格
}
int main(){
PrintDouble(7.5, std::string('hello'), 10); // 等價(jià)調用print(7.5 + 7.5, std::string('hello') + std::string('hello'), 10 + 10);
addOne(1, 2, 3); // 輸出 2 3 4
}
可變參數包可以出現在下標中,用于訪(fǎng)問(wèn)指定下標的元素。
可變參數包可以為非類(lèi)型模板參數。
template<typename C, typename ...Index>
void printElems(const C &coll, Index ...idx)
{
print(coll[idx]...);
}
template<typename C, std::size_t ...Idx> // 參數包為非類(lèi)型模板參數
void printIndex(const C &coll)
{
print(coll[Idx]...);
}
int main(){
std::vector<std::string> coll = {'wyl', 'sjx', 'love'};
printElems(coll, 2, 0, 1); //相當于調用print(coll[2], coll[0], coll[1]);
printIndex<std::vector<std::string>, 2, 0, 1>(coll); // 等同于以上調用
}
template <typename... T>
class Tuple;
Tuple<int, char> t; // 可以表示數據成員的類(lèi)型
template <typename... T>
class Variant;
Variant<int, double, bool, float> v; // 可以表示對象可能的類(lèi)型
5.推斷指引也可以是可變參數的。
namespace std {
// std::array a{42,43,44} 會(huì )被推斷為 std::array<int,3> a{42,43,44}
template<typename T, typename… U> array(T, U…) -> array<enable_if_t<(is_same_v<T, U> && …), T>, (1 + sizeof…(U))>;
}
1.c++規定模板中通過(guò)域作用符訪(fǎng)問(wèn)的嵌套從屬名稱(chēng)不是類(lèi)型名稱(chēng),為了表明該名稱(chēng)是類(lèi)型,需要加上 typename 關(guān)鍵字。
template<typename T>
class MyClass
{
public:
void foo()
{
/* 若無(wú)typename SubType會(huì )被當做T中的一個(gè)static成員或枚舉值
下方的表達式被理解為SubType 和 ptr的乘積 */
typename T::SubType *ptr;
//...
}
};
1.c++中對于未定義默認構造函數的類(lèi)型對象,定義時(shí)一般不會(huì )進(jìn)行默認初始化,這時(shí)候對象的值將是未定義的。
2.在模板中定義對象時(shí),為了避免產(chǎn)生未定義的行為,可以進(jìn)行零初始化。
template<typename T>
void foo()
{
T x = T(); // 對x提供默認值
}
1.若類(lèi)模板的基類(lèi)也是類(lèi)模板,這時(shí)在類(lèi)模板中不能直接通過(guò)名稱(chēng)調用從基類(lèi)繼承的成員,而應該通過(guò) this-> 或 Base::。
template<typename T>
class Base
{
public:
void bar(){};
};
template<typename T>
class Derived:public Base<T>
{
public:
void foo(){
bar(); //ERROR 無(wú)法訪(fǎng)問(wèn)基類(lèi)bar成員,若global存在bar函數,則會(huì )訪(fǎng)問(wèn)全局bar函數
this->bar(); // ok
Base<T>::bar(); // ok
}
};
1.當向模板傳遞裸數組或字符串常量時(shí),如果是引用傳遞,則類(lèi)型不會(huì ) decay;如果是值傳遞,則會(huì ) decay。
2.也可以通過(guò)將數組或字符串長(cháng)度作為非類(lèi)型模板參數,定義可以適配不同長(cháng)度的裸數組或字符串常量的模板。
template <typename T>
void foo(T t){};
template <typename T>
void RefFoo(const T &t){};
template<typename T, int N, int M>
bool less (T(&a)[N], T(&b)[M])
{
for (int i = 0; i<N && i<M; ++i)
{
if (a[i]<b[i]) return true; if (b[i]<a[i]) return false;
}
return N < M;
}
int main(){
foo('hello'); // T 為const char *
RefFoo('hello'); // T 為const char[6]
less('sjx', 'wyl'); // T 為const char, N,M為3
}
1.不管類(lèi)是普通類(lèi)還是類(lèi)模板,類(lèi)中的成員函數都可以定義為模板。
2.若是將構造函數或者賦值運算符定義為模板函數,此時(shí)定義的模板函數不會(huì )取代默認的的構造函數和賦值運算符。下面定義的 operater = 只能用于不同類(lèi)型的 stack 之間的賦值,若是相同類(lèi)型,仍然采用默認的賦值運算符。
#include<deque>
template <typename T, typename COND = std::deque<T> >
class Stack
{
public:
void push(const T& value);
void pop();
T top();
int size() const { elem_.size(); };
bool empty() const { return elem_.empty(); };
//實(shí)現不同類(lèi)型的stack之間的相互賦值
template <typename T2>
Stack<T,COND> & operator = (const Stack<T2>& s);
protected:
COND elem_;
};
template <typename T, typename COND>
template <typename T2>
Stack<T,COND> & Stack<T,COND>::operator = (const Stack<T2>& s)
{
if((void *)this == (void *)&s)
{
return *this;
}
Stack<T2> tmp(s);
elem_.clear();
while(!tmp.empty())
{
elem_.push_front(tmp.top());
tmp.pop();
}
return *this;
}
2.成員函數模板也可以被偏特化或者全特化。
#include<string>
class BoolString {
private:
std::string value;
public:
BoolString (std::string const& s): value(s) {}
template<typename T = std::string>
T get() const {
return value;
}
};
// 進(jìn)行全特化, 全特化版本的成員函數相當于普通函數,
// 放在頭文件中會(huì )導致重復定義,因此必須加inline
template<>
inline bool BoolString::get<bool>() const
{
return value == 'true' || value == '1';
}
int main()
{
BoolString s1('hello');
s1.get(); // 'hello'
s1.get<bool>(); // false
}
3.當通過(guò)依賴(lài)于模板參數的對象通過(guò).調用函數模板時(shí),需要通過(guò) template 關(guān)鍵字提示<是成員函數模板參數列表的開(kāi)始,而非<運算符。
#include<bitset>
#include<iostream>
template<unsigned int N>
void printBitSet(const std::bitset<N> &bs)
{
// bs依賴(lài)于模板參數N 此時(shí)為了表明to_string后是模板參數,需要加template
std::cout << bs.template to_string<char,std::char_traits<char>,std::allocator<char> >() << std::endl;
}
4.c++14 引入的泛型 lambda 是對成員函數模板的簡(jiǎn)化。
[](auto x, auto y)
{
return x + y;
}
// 編譯將以上lamba轉化為下方類(lèi)
class SomeCompilerSpecificName {
public:
SomeCompilerSpecificName();
template<typename T1, typename T2>
auto operator() (T1 x, T2 y) const {
return x + y;
}
};
1.c++14 開(kāi)始,可以通過(guò)變量模板對變量進(jìn)行參數化。
2.變量模板的常見(jiàn)應用場(chǎng)景是定義代表類(lèi)模板成員的變量模板。
3.c++17 開(kāi)始,標準庫用變量模板為其用來(lái)產(chǎn)生一個(gè)值(布爾型)的類(lèi)型萃取定義了簡(jiǎn)化方式。
#include<iostream>
template <typename T = double>
constexpr T pi{3.1415926};
std::cout<< pi<> <<std::endl; // <>不可少,輸出3.1415926
std::cout<< pi<int> <<std::endl; // 輸出3
template<typename T>
class MyClass {
public:
static constexpr int max = 1000; // 類(lèi)靜態(tài)成員
};
// 定義變量模板表示類(lèi)靜態(tài)成員
template <typename T>
int myMax = MyClass<T>::max;
// 使用更方便
auto i = myMax<int>; // 相當于auto i = MyClass<int>::max;
// 萃取簡(jiǎn)化方式,since c++17
namespace std {
template<typename T>
constexpr bool is_const_v = is_const<T>::value;
}
1.當非類(lèi)型模板參數是一個(gè)模板時(shí),我們稱(chēng)它為模板模板參數。
2.實(shí)例化時(shí),模板模板參數和實(shí)參的模板參數必須完全匹配。
#include <deque>
#include <vector>
// 錯誤定義 deque 中的模板參數有兩個(gè):類(lèi)型和默認參數allocator
// 而模板模板參數Cont的參數只有類(lèi)型Elem
template<typename T,
template<typename Elem> class Cont = std::deque>
class Stack {
private:
Cont<T> elems; // elements
public:
void push(T const&); // push element
void pop(); // pop element
T const& top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
// 正確定義
template<typename T, template<typename Elem, typename =
std::allocator<Elem>> class Cont = std::deque>
class Stack {
private:
Cont<T> elems; // elements
public:
void push(T const&); // push element
void pop(); // pop element
T const& top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
// 使用
Stack<int> iStack;
Stack<double,std::vector> dStack;
1.c++11 引入了引用折疊:創(chuàng )建一個(gè)引用的引用,引用就會(huì )折疊。除了右值引用的右值引用折疊之后還是右值引用之外,其它的引用全部折疊成左值引用。
2.基于引用折疊和 std::forward,可以實(shí)現完美轉發(fā):將傳入將被參數的基本特性(是否 const,左值、右值引用)轉發(fā)出去。
#include <utility>
#include <iostream>
class X {
};
void g (X&) {
std::cout << 'g() for variable\n';
}
void g (X const&) {
std::cout << 'g() for constant\n';
}
void g (X&&) {
std::cout << 'g() for movable object\n';
}
template<typename T>
void f (T&& val) {
g(std::forward<T>(val));
}
int main()
{
X v; // create variable
X const c; // create constant
f(v); // f() for variable calls f(X&) => calls g(X&)
f(c); // f() for constant calls f(X const&) => calls g(X const&)
f(X()); // f() for temporary calls f(X&&) => calls g(X&&)
f(std::move(v)); // f() for move-enabled variable calls f(X&&)=>calls g(X&&)
}
1.當類(lèi)中定義了模板構造函數時(shí):
a.定義的模板構造函數不會(huì )屏蔽默認構造函數。
b.優(yōu)先選用匹配程度高的構造函數
c.匹配程度相同時(shí),優(yōu)先選用非模板構造函數
#include <utility>
#include <string>
#include <iostream>
class Person
{
private:
std::string name;
public:
// generic constructor for passed initial name:
template<typename STR>
explicit Person(STR&& n) : name(std::forward<STR>(n)) {
std::cout << 'TMPL-CONSTR for ’' << name <<std::endl;
}
// copy and move constructor:
Person (Person const& p) : name(p.name) {
std::cout << 'COPY-CONSTR Person ’' << name << '’\n';
}
Person (Person&& p) : name(std::move(p.name)) {
std::cout << 'MOVE-CONSTR Person ’' << name << '’\n';
}
};
int main()
{
Person p1('tmp'); // ok 調用模板函數n 被推斷為const char [4]類(lèi)型,可以賦值給string
Person p2(p1); // error 模板函數相比Person (Person const& p)構造函數更匹配,n 被推斷為Person &類(lèi)型,最終將Person對象賦值給string
Person p3(std::move(p1)); // ok 同等匹配程度時(shí),優(yōu)先調用Person (Person&& p)
}
1.c++11 提供了輔助模板 std::enable_if,使用規則如下:
a.第一個(gè)參數是布爾表達式,第二個(gè)參數為類(lèi)型。若表達式結果為 true,則 type 成員返回類(lèi)型參數,此時(shí)若未提供第二參數,默認返回 void。
b.若表達式結果為 false,根據替換失敗并非錯誤的原則,包含 std::enable_if 的模板將會(huì )被忽略。
2.c++14 提供了別名模板技術(shù)(見(jiàn) 2.8 節),可以用 std::enable_if_t<>代替 std::enable_if<>::type.
3.若不想在聲明中使用 std::enable_if,可以提供一個(gè)額外的、有默認值的模板參數。
#include <type_traits>
template<typename T>
typename std::enable_if<(sizeof(T) > 4)>::type
foo1(){}
// std::enable_if為true時(shí)等同下方函數,未提供第二參數默認返回void; false時(shí)函數模板被忽略
// void foo1(){};
template<typename T>
typename std::enable_if<(sizeof(T) > 4), bool>::type
foo2(){}
// std::enable_if為true時(shí)等同下方函數,false時(shí)函數模板被忽略
// bool foo2(){};
// 提供額外默認參數進(jìn)行類(lèi)型檢查
template<typename T, typename = typename std::enable_if<(sizeof(T) > 4)>::type>
void foo3(){}
// std::enable_if為true時(shí)等同下方函數模板,false時(shí)函數模板被忽略
// template<typename T, typename = void>
// void foo3(){};
1.通過(guò) std::enable_if 和標準庫的類(lèi)型萃取 std::is_convertiable<FROM, TO>可以解決 6.2 節構造函數模板的問(wèn)題。
class Person
{
private:
std::string name;
public:
// 只有STR可以轉換為string時(shí)才有效
template<typename STR, typename = typename std::enable_if<std::is_convertible<STR, std::string>::value>::type>
explicit Person(STR&& n) : name(std::forward<STR>(n)) {
std::cout << 'TMPL-CONSTR for ’' << name <<std::endl;
}
// copy and move constructor:
Person (Person const& p) : name(p.name) {
std::cout << 'COPY-CONSTR Person ’' << name << '’\n';
}
Person (Person&& p) : name(std::move(p.name)) {
std::cout << 'MOVE-CONSTR Person ’' << name << '’\n';
}
};
1.c++20 提出了 concept 模板可以進(jìn)行編譯期條件檢查,大大簡(jiǎn)化了 enable_if
template <typename T>
concept convert_to_string = std::is_convertible<STR, std::string>::value;
class Person
{
private:
std::string name;
public:
// 只有STR可以轉換為string時(shí)才有效
template<convert_to_string STR>
explicit Person(STR&& n) : name(std::forward<STR>(n)) {
std::cout << 'TMPL-CONSTR for ’' << name <<std::endl;
}
// copy and move constructor:
Person (Person const& p) : name(p.name) {
std::cout << 'COPY-CONSTR Person ’' << name << '’\n';
}
Person (Person&& p) : name(std::move(p.name)) {
std::cout << 'MOVE-CONSTR Person ’' << name << '’\n';
}
};
1.當函數參數按值傳遞時(shí),原則上所有參數都會(huì )被拷貝。
2.當傳遞的參數是純右值時(shí),編譯器會(huì )優(yōu)化,避免拷貝產(chǎn)生;從 c++17 開(kāi)始,要求此項優(yōu)化必須執行。
3.按值傳遞時(shí),參數會(huì )發(fā)生 decay,如裸數組退化為指針,const、volatile 等限制符會(huì )被刪除。
#include <string>
template <typename T>
void foo(T t){};
int main(){
std::string s('Sjx');
foo(s); // 進(jìn)行拷貝
foo(std::string('sjx')); // 避免拷貝
foo('sjx'); // T decay為const char *
}
1.當函數參數按照引用傳遞時(shí),不會(huì )被拷貝,并且不會(huì ) decay。
2.當函數參數按照引用傳遞時(shí),一般是定義為 const 引用,此時(shí)推斷出的類(lèi)型 T 中無(wú) const。
3.當參數定義為非常量引用時(shí),說(shuō)明函數內部可以修改傳入參數,此時(shí)不允許將臨時(shí)變量傳給左值引用。此時(shí)若傳入參數是 const 的,則類(lèi)型 T 被推斷為 const,此時(shí)函數內部的參數修改將報錯.
template <typename T>
void print(const T &t){};
template <typename T>
void out(T &t){};
template <typename T>
void modify(T &t)
{
t = T();
};
int main(){
print('sjx'); // arg為const char[3], T 為char[3]
std::string s('Sjx');
out(s); // ok
out(std::string('sjx')); // error 不能將臨時(shí)變量傳給左值引用
const std::string name = 'sjx';
modify(name); //error T 被推斷為 const std::string, 此時(shí)modify不能修改傳入參數
}
4.對于給非 const 引用參數傳遞 const 對象導致編譯失敗的情形,可以通過(guò) static_assert std::enable_if 或者 concept 等方式進(jìn)行檢查。
// static_assert 觸發(fā)編譯期錯誤
template <typename T>
void modify(T &t)
{
static_assert(!std::is_const<T>::value, 'param is const');
t = T();
};
// std::enable_if禁用模板
template <typename T, typename = typename std::enable_if<!std::is_const<T>::value> >
void modify(T &t)
{
t = T();
};
// concept禁用模板
template <typename T>
concept is_not_const = !std::is_const<T>::value;
template <is_not_const T>
void modify(T &t)
{
t = T();
};
5.當完美轉發(fā)時(shí),會(huì )將函數參數定義為右值引用。此時(shí)若在函數內部用 T 定義未初始化的變量,會(huì )編譯失敗。
template<typename T>
void passR(T &&t)
{
T x;
}
std::string s('Sjx');
passR(s); // error T 推斷為string &,初始化時(shí)必須綁定到對象
1.c++11 開(kāi)始,若模板參數定義為按值傳遞時(shí),調用者可以通過(guò) std::cref 或 std::ref 將參數按照引用傳遞進(jìn)去。
2.std::cref 或 std::ref創(chuàng )建了一個(gè) std::reference_wrapper<>的對象,該對象引用了原始參數,并被按值傳遞給了函數模板。std::reference_wrapper<>對象只支持一個(gè)操作:向原始對象的隱式類(lèi)型轉換。
template<typename T>
void foo(T arg)
{
T x;
}
int main(){
std::string s = 'hello';
foo(s); // T 為std::string
foo(std::cref(s)); // error T 為std::reference_wrapper<std::string> 只支持向std::string的類(lèi)型轉換
}
1.字符串常量或裸數組傳遞給模板時(shí),如果是按值傳遞,則會(huì ) decay;如果是按照引用傳遞,則不會(huì ) decay。實(shí)際應用時(shí),可以根據函數作用加以選擇,若要比較大小,一般是按照引用傳遞;若是比較參數類(lèi)型是否相同,則可以是按值傳遞。
1.函數返回值也可以是按值返回或按引用返回。若返回類(lèi)型為非常量引用,則表示可以修改返回對象引用的對象。
2.模板中即使使用 T 作為返回類(lèi)型,也不一定能保證是按值返回。
template<typename T>
T retR(T &&p)
{
return T();
}
template<typename T>
T retV(T t)
{
return T();
}
int main()
{
int x;
retR(x); // error T 被推斷為int &
retV<int &>(x); // error T推斷為int &
}
3.為保證按值返回,可以使用 std::remove_reference<>或 std::decay<>。
1.一般通常按值傳遞,如有特殊需要,可以結合實(shí)際按引用傳遞。
2.定義的函數模板要明確使用范圍,不要過(guò)分泛化。
1.模板元編程:在編譯期通過(guò)模板實(shí)例化的過(guò)程計算程序結果。
/* 定義用于編譯期判斷素數的模板 */
template<unsigned p ,unsigned d>
struct DoIsPrime
{
// 從p%d !=0 開(kāi)始依次判斷 p%(d-1)!=0,p%(d-2)!=0,...
static constexpr bool value = (p % d != 0) && DoIsPrime<p, d-1>::value;
};
// 遞歸基
template<unsigned p>
struct DoIsPrime<p ,2>
{
static constexpr bool value = (p % 2 != 0);
};
// 提供給用戶(hù)調用的模板
template<unsigned p>
struct IsPrime
{
static constexpr bool value = DoIsPrime<p, p / 2>::value;
};
// 對于p/2<2的情形進(jìn)行特化
template<>
struct IsPrime<0> {
static constexpr bool value = false;
};
template<>
struct IsPrime<1> {
static constexpr bool value = false;
};
template<>
struct IsPrime<2> {
static constexpr bool value = false;
};
template<>
struct IsPrime<3> {
static constexpr bool value = false;
};
int main(){
IsPrime<9>::value; // 編譯期通過(guò)層層遞歸實(shí)例化最后得到結果為false
}
1.c++11 提出了 constexpr 關(guān)鍵字可以用于修飾函數返回值,此時(shí)該函數為常量表達式函數,編譯器可以在編譯期完成該函數的計算。
2.c++11 中規定常量表達式函數在使用前必須要知道完整定義,不能僅僅是聲明,同時(shí)函數內部只能有一條返回語(yǔ)句。
3.c++14 中移除了常量表達式函數只能有一條返回語(yǔ)句的限制。
4.編譯器可以在編譯期完成該函數計算,但是是否進(jìn)行還取決于上下文環(huán)境及編譯器。
// constexpr函數只能有一個(gè)return語(yǔ)句
constexpr bool doIsPrime(unsigned p, unsigned d)
{
return d != 2 ? (p % d != 0) && doIsPrime(p, d - 1) : (p % 2 != 0);
}
constexpr bool isPrime(unsigned p)
{
return p < 4 ? !(p < 2) : doIsPrime(p, p / 2);
}
int main(){
constexpr bool ret = isPrime(9); // constexpr 限定必須在編譯期得到結果
}
1.可以通過(guò)模板偏特化在不同的實(shí)現方案之間做選擇。
// 根據是否是素數判斷是否喜歡
template <unsigned int SZ, bool = isPrime(SZ)>
struct IsLove;
// 特化兩個(gè)版本處理
template< unsigned int SZ>
struct IsLove<SZ, false>
{
static constexpr bool isLove = false;
};
template< unsigned int SZ>
struct IsLove<SZ, true>
{
static constexpr bool isLove = true;
};
int main()
{
IsLove<520>::isLove; // 編譯期就知道了520不是喜歡
}
SFINAE:當函數調用的備選方案中出現函數模板時(shí),編譯器根據函數參數確定(替換)函數模板的參數類(lèi)型及返回類(lèi)型,最后評估替換后函數的匹配程度。替換過(guò)程中可能失敗,此時(shí)編譯器會(huì )忽略掉這一替換結果。
替換和實(shí)例化不同,替換只涉及函數函數模板的參數類(lèi)型及返回類(lèi)型,最后編譯器選擇匹配程度最高的函數模板進(jìn)行實(shí)例化。
#include<vector>
#include<iostream>
// 返回裸數組長(cháng)度的模板,只有用裸數組替換時(shí)才能成功
template<typename T, unsigned N>
std::size_t len (T(&)[N])
{
return N;
}
// 只有含有T::size_type的類(lèi)型才能替換成功
template<typename T>
typename T::size_type len (T const& t)
{
return t.size();
}
// ... 表示為可變參數,匹配所有類(lèi)型, 但匹配程度最差
std::size_t len(...)
{
return 0;
}
int main()
{
int a[10];
std::cout << len(a) <<std::endl; // 匹配裸數組
std::cout << len('sjx') <<std::endl; // 匹配裸數組
std::vector<int> v;
std::cout << len(v) <<std::endl; // T::size_type
int *p;
std::cout << len(p) <<std::endl; // 函數模板均不匹配,最后調用變參函數
std::allocator<int> x;
/* std::allocator 定義了size_type,所以匹配T::size_type和變參函數,
前者匹配程度更高,因此選擇該模板。但在實(shí)例化時(shí)會(huì )發(fā)現allocator不存在size成員 */
std::cout << len(x) <<std::endl; //error
}
/* decltype中采用逗號表達式,只有若T中不存在size成員,則替換失敗。
替換成功時(shí),才會(huì )將逗號表達式最后一句作為返回類(lèi)型 */
template<typename T>
auto len (T const& t) -> decltype((void)(t.size()) , T::size_type)
{
return t.size();
}
// 修改4.1中的print函數
template<typename T, typename… Types>
void print (T const& firstArg, Types const&… args)
{
std::cout << firstArg << std::endl; // print只有一個(gè)參數時(shí),只會(huì )編譯這一部分
if constexpr(sizeof…(args) > 0) {
print(args…); // 只有args不為空的時(shí)候才會(huì )繼續遞歸實(shí)例化
C++17)
}
}
template<typename T>
void foo(const T& t){};
// 全特化函數
template<>
inline void foo<int>(const int& i){};
預編譯頭文件不在 c++標準要求中,具體由編譯器實(shí)現。
預編譯頭文件:如果多個(gè)代碼文件的前 n 行均相同,編譯器就可以先對前 n 行進(jìn)行編譯,再依次對每個(gè)文件從 n+1 行進(jìn)行編譯。
為了充分利用預編譯頭文件功能,實(shí)踐中建議文件中#include 的順序盡量相同。
替換:在用模板實(shí)參去查找匹配的模板時(shí),會(huì )嘗試用實(shí)參去替換模板參數,見(jiàn) 8.4 節。
實(shí)例化:查找到最匹配的模板后,根據實(shí)參從模板創(chuàng )建出常規類(lèi)或函數的過(guò)程。
特例化:對模板中的部分或全部參數進(jìn)行特化,定義新模板的過(guò)程。
聲明:將一個(gè)名稱(chēng)引入 c++作用域內,并不需要知道名稱(chēng)的相關(guān)細節。
定義:如果在聲明時(shí)提供了細節,聲明就變成了定義。
// 聲明
class A;
extern int v;
void f();
// 定義
class A{};
int v = 1;
void f(){};
a.常規(比如非模板)非 inline 函數和成員函數,以及非 inline 的全局變量和靜態(tài)數據成員,在整個(gè)程序中只能被定義一次.
b. Class 類(lèi)型(包含 struct 和 union),模板(包含部分特例化,但不能是全特例化),以及 inline 函數和變量,在一個(gè)編譯單元中只能被定義一次,而且不同編譯單元間的定義 應該相同.
1.模板參數:模板定義中模板參數列表中的參數。
a.函數指針
b. 仿函數
c. 存在一個(gè)函數指針或者函數引用的轉換函數的 class 類(lèi)型
#include <iostream>
#include <vector>
template<typename Iter, typename Callable>
void foreach (Iter current, Iter end, Callable op)
{
while (current != end) { //as long as not reached the end
op(*current); // call passed operator for current element
++current; // and move iterator to next element
}
}
// a function to call:
void func(int i)
{
std::cout << 'func() called for: ' << i << std::endl;
}
// a function object type (for objects that can be used as functions):
class FuncObj {
public:
void operator() (int i) const { //Note: const member function
std::cout << 'FuncObj::op() called for: ' << i << std::endl;
}
};
int main()
{
std::vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19};
foreach (primes.begin(), primes.end(), func); // function as callable (decays to pointer)
foreach(primes.begin(), primes.end(), &func); // function pointer as callable
foreach(primes.begin(), primes.end(), FuncObj()); // function object as callable
foreach(primes.begin(), primes.end(), [] (int i) { //lambda as callable
std::cout << 'lambda called for: ' << i << std::endl;});
}
#include <type_traits>
template<typename T>
class C
{
// ensure that T is not void (ignoring const or volatile):
static_assert(!std::is_same_v<std::remove_cv_t<T>,void>,
'invalid instantiation of class C for void type');
public:
template<typename V>
void f(V&& v) {
if constexpr(std::is_reference_v<T>) { … // special code if T is a reference type
}
if constexpr(std::is_convertible_v<std::decay_t<V>,T>) { … // special code if V is convertible to T
}
if constexpr(std::has_virtual_destructor_v<V>) { … // special code if V has virtual destructor
}
}
};
std::addressof<>()會(huì )返回一個(gè)對象或者函數的準確地址,即使一個(gè)對象重載了取地址運算符&也是這樣。
函數模板 std::declval()可以被用作某一類(lèi)型的對象的引用的占位符。
// 避免在調用運算符?:的時(shí)候不得不去調用 T1 和 T2 的(默認)構造函數,這里使用了std::declval
#include <utility>
template<typename T1, typename T2,
typename RT = typename std::decay_t< decltype(true ? std::declval<T1>() : std::declval<T2>())> >
RT max (T1 a, T2 b)
{
return b < a ? a : b;
}
template<typename T>
void foo(T x)
{
auto&& val = get(x);
// perfectly forward the return value of get() to set():
set(std::forward<decltype(val)>(val));
}
template<typename T, T Z = T{}>
class RefMem {
private:
T zero;
public:
RefMem() : zero{Z} {}
};
int null = 0;
int main()
{
RefMem<int> rm1, rm2;
rm1 = rm2; // OK
RefMem<int&> rm3; // ERROR: 無(wú)法用int & 對Z進(jìn)行默認初始化
RefMem<int&, 0> rm4; // ERROR: 不能用0 初始化int & 對象zero
RefMem<int&,null> rm5, rm6;
rm5 = rm6; // ERROR: 具有非static引用成員的類(lèi),默認賦值運算符會(huì )被刪掉
}
2.如果需要禁止引用類(lèi)型進(jìn)行實(shí)例化,可以使用 std::is_reference 進(jìn)行判斷。
template<typename T>
class Cont {
private:
T* elems;
public:
};
struct Node
{
std::string value;
Cont<Node> next; // only possible if Cont accepts incomplete types
};
template<typename T> // a namespace scope class template
class Data {
public:
static constexpr bool copyable = true;
};
template<typename T> // a namespace scope function template
void log (T x) {
}
template<typename T> // a namespace scope variable template (since C++14)
T zero = 0;
template<typename T> // a namespace scope variable template (since C++14)
bool dataCopyable = Data<T>::copyable;
template<typename T> // a namespace scope alias template
using DataList = Data<T*>;
class Collection {
public:
template<typename T> // an in-class member class template definition
class Node {
};
template<typename T> // an in-class (and therefore implicitly inline)
T* alloc() { // member function template definition
}
template<typename T> // a member variable template (since C++14)
static T zero = 0;
template<typename T> // a member alias template
using NodePtr = Node<T>*;
};
template<typename T>
union AllocChunk {
T object;
unsigned char bytes[sizeof(T)];
};
3.在類(lèi)模板內,也可以定義和模板參數無(wú)關(guān)的非模板成員。
4.類(lèi)內的模板函數不能是虛函數,普通函數可以是虛函數。
template<int I>
class CupBoard
{
public:
class Shelf; // ordinary class in class template
void open(); // ordinary function in class template
enum Wood{}; // ordinary enumeration type in class template
static double totalWeight; // ordinary static data member in class template
virtual void fun(){}; // 普通函數可以是虛函數
virtual ~CupBoard(){}; // 析構函數也可以是虛函數
template <typename U>
virtual void foo(const U &u){}; // error 模板函數不能是虛函數
};
每一個(gè)模板名稱(chēng)需要在所在作用域內獨一無(wú)二。
函數模板可以有 c++鏈接,但不能有 C 鏈接。
函數模板一般具有外部鏈接,除非是 static 或定義在未命名的命名空間中。
int X;
template <typename T>
class X; // error 模板名稱(chēng)必須獨一無(wú)二
extern 'C++' template<typename T>
void normal(); // ok
extern 'C' template <typename T>
void invalid(); // error 模板函數不能有C鏈接
template<typename T>
void external(); // 指向另一文件中定義的external函數模板
template<typename T>
static void internal(); // 并未指向另一文件中定義的internal函數模板,需要在本文件內提供定義
static void internal();
namespace {
template<typename>
void otherInternal(); // 并未指向另一文件中定義的otherInternal函數模板
}
namespace {
template<typename>
void otherInternal(); // 并未指向另一文件中定義的otherInternal函數模板
}
2.非類(lèi)型模板參數可以是以下形式:
a.整型或枚舉類(lèi)型
b.指針類(lèi)型
c.指向類(lèi)成員的指針
d.左值引用
e.std::nullptr_t
f.含有 auto 或者 decltype(auto)的類(lèi)型(since c++17)
4. c++11 開(kāi)始,可以通過(guò)...定義模板參數包,匹配任意類(lèi)型和數目的參數。
5. 也可以定義指定類(lèi)型的非模板參數包,匹配指定類(lèi)型任意數目的參數。
6. 模板可以提供模板參數的默認值,一旦為一個(gè)參數提供默認值,其后的參數都必須已經(jīng)定義默認值。
7. 若一個(gè)模板存在多處聲明或聲明和定義同時(shí)存在,那么可以在這些地方定義模板參數的默認值,不能為一個(gè)參數重復定義默認值。
template<template<typename X> class C> // ok
void f(C<int>* p);
template<template<typename X> struct C> // error
void f(C<int>* p);
template<template<typename X> union C> // error
void f(C<int>* p);
template<template<typename X> typename C> // ok since c++17
void f(C<int>* p);
template<template<typename T, T*> class Buf> // OK
class Lexer
{
static T* storage; // 模板模板參數的參數不能在聲明之外使用
};
template<typename ...Types> // 模板參數包可以匹配任意參數
class Tuple{};
Tuple<int> t1; // 匹配int
Tuple<int, char> t2; // 匹配int char
template<typename T, unsigned... Dimensions> // 非類(lèi)型模板參數包匹配任意數目的unsigned參數
class MultiArray {};
MultiArray<double, 3, 3> matrix; // 定義矩陣
template<typename T1 = int, typename T2> // error T2未定義默認值
class Test;
template<typename T1, typename T2 = double> // ok
class Quintuple;
template<typename T1 = int, typename T2> // ok T2默認值前面已經(jīng)定義
class Quintuple;
template<typename T1 = int> // error 不能重復定義默認值
class Quintuple;
函數模板一般可以通過(guò)模板實(shí)參來(lái)推斷模板參數,但也存在無(wú)法推斷的情形。
模板模板參數的實(shí)參必須完全匹配模板模板參數。
#include <vector>
template<typename DstT, typename SrcT>
DstT implicit_cast (SrcT const& x) // SrcT can be deduced, but DstT cannot
{
return x;
}
implicit_cast<double> (0); // 需要指定返回類(lèi)型,若DstT和SrcT順序調換,使用時(shí)需要指定兩個(gè)類(lèi)型
template<typename ... Ts, int N>
void f(double (&)[N+1], Ts ... ps); // useless declaration because N cannot be specified or deduced
template<typename T, template<typename U> class Cond>
class MyCond;
MyCond<int,std::vector> c; // error std::vector 為template<class _Tp, class _Alloc>,雖然_Alloc為默認參數,但仍然不匹配
將類(lèi)模板作為友元時(shí),必須保證友元定義位置已經(jīng)知道類(lèi)模板的聲明。
也可以將類(lèi)型模板參數定義為友元 。
可以將函數模板定義為友元,此時(shí)若模板參數可推導,在友元聲明時(shí)可以省略。
template<typename T>
class Tree {
friend class Factory; // OK even if first declaration of Factory
friend class MyNode<T>; // error MyNode未找到聲明
};
template<typename T>
class Stack {
public:
// assign stack of elements of type T2
template<typename T2>
Stack<T>& operator= (Stack<T2> const&);
// to get access to private members of Stack<T2> for any type T2: template<typename> friend class Stack;
};
template<typename T>
class Test
{
friend T; // 可以將類(lèi)型模板參數定義為友元
};
template<typename T1, typename T2>
void combine(T1, T2);
class Mixer {
friend void combine<>(int&, int&); //OK:T1 = int&,T2 = int&
friend void combine<int, int>(int, int);//OK:T1 = int,T2 = int
friend void combine<char>(char, int);//OK:T1 = charT2 = int
friend void combine<char>(char&, int);// ERROR: doesn’t match combine() template
friend void combine<>(long, long) { } // ERROR: definition not allowed!
};
1.名稱(chēng)分為受限名稱(chēng)和非受限名稱(chēng),受限名稱(chēng)前面有顯式的出現 ->, ::, . 這三個(gè)限定符
c++名稱(chēng)的普通查找規則為從名稱(chēng)所在的 scope 從內向外依次查找。
ADL( Argument-Dependent Lookup)查找為依賴(lài)于參數的查找,是用于函數調用表達式中查找非限定函數名稱(chēng)的規則。當在使用函數的上下文中找不到函數定義,我們可以在其參數的關(guān)聯(lián)類(lèi)和關(guān)聯(lián)名字空間中查找該函數的定義。
ADL 生效條件:a.使用此規則的函數必須要有參數 b. 使用此規則的函數名稱(chēng)必須為非受限名稱(chēng)
4.ADL 查找范圍:
(1)對于基本類(lèi)型(int, char 等), 該集合為空集
(2)對于指針和數組類(lèi)型,該集合是所引用類(lèi)型的關(guān)聯(lián)類(lèi)和關(guān)聯(lián)名字空間
(3)對于枚舉類(lèi)型,名字空間是名字空間是枚舉聲明所在的名字空間,對于類(lèi)成員,關(guān)聯(lián)類(lèi)是枚舉所在的類(lèi)
(4)對于 class(包含聯(lián)合類(lèi)型),關(guān)聯(lián)類(lèi)包括該類(lèi)本身,他的外圍類(lèi),直接基類(lèi),間接基類(lèi)。關(guān)聯(lián)名字空間包括每個(gè)關(guān)聯(lián)類(lèi)所在的名字空間。
(5)對于函數類(lèi)型, 該集合包含所有參數類(lèi)型和返回類(lèi)型的關(guān)聯(lián)類(lèi)和關(guān)聯(lián)名字空間
(6)對于類(lèi) X 的成員指針類(lèi)型,除了包括成員相關(guān)的關(guān)聯(lián)名字空間,關(guān)聯(lián)類(lèi),該集合還包括與 X 相關(guān)的關(guān)聯(lián)名字空間和關(guān)聯(lián)類(lèi)
#include <iostream>
namespace X {
template<typename T> void f(T);
}
namespace N {
using namespace X;
enum E { e1 };
void f(E) {
std::cout << 'N::f(N::E) called\n';
}
}
void f(int)
{
std::cout << '::f(int) called\n';
}
int main()
{
::f(N::e1); // qualified function name: no ADL
f(N::e1); // ordinary lookup finds ::f() and ADL finds N::f(),the latter is preferred
}
template<typename T>
class C {
friend void f();
friend void f(C<T> const&);
};
void g (C<int>* p)
{
f(); // error 類(lèi)內聲明的友元類(lèi)外不可見(jiàn),普通查找無(wú)法找到
f(*p); // ok 雖然不可見(jiàn),但是可以通過(guò)ADL找到
}
int X;
struct X {
void f() {
X* p; // OK:X 指代注入類(lèi)名
::X* q; // 錯誤:名稱(chēng)查找找到變量名,它隱藏 struct 名
}
};
template<typename T> class C {
using Type = T;
struct J {
C* c; // C refers to a current instantiation
C<Type>* c2; // C<Type> refers to a current instantiation
I* i; // I refers to an unknown specialization,
// because I does not enclose J
J* j; // J refers to a current instantiation
};
struct I {
C* c; // C refers to a current instantiation
C<Type>* c2; // C<Type> refers to a current instantiation
I* i; // I refers to a current instantiation
};
};
大多數編程語(yǔ)言的編譯器的兩個(gè)基本活動(dòng)是標記化和解析。
X<1>(0) ;被解析時(shí),若 X 是一個(gè)類(lèi)型,<1>則被認為是模板實(shí)參;若 X 不是類(lèi)型,則被認為是 x<1 比較后的結果再和>0 比較。在處理模板時(shí),一定要避免大于符號>被當做模板參數終止符。
template<bool B>
class Invert {
public:
static bool const result = !B;
};
void g()
{
bool test = Invert<1>0>::result; // error 模板參數被提前結束
bool test = Invert<(1>0)>::result; // ok
}
a. 名稱(chēng)出現在一個(gè)模板中
b. 名稱(chēng)是受限的
c. 名稱(chēng)不是用于基類(lèi)的派生列表或構造函數的初始化列表中
d. 名稱(chēng)依賴(lài)于模板參數
namespace N {
class X {
};
template<int I> void select(X*);
}
void g (N::X* xp)
{
select<3>(xp); // ERROR 編譯無(wú)法知道<3>是模板參數,進(jìn)而無(wú)法判斷select是函數調用
}
2.非依賴(lài)型基類(lèi):無(wú)需知道模板名稱(chēng)就可以完全確定類(lèi)型的基類(lèi)。
3.非依賴(lài)型基類(lèi)的派生類(lèi)中查找一個(gè)非受限名稱(chēng)時(shí),會(huì )先從非依賴(lài)型基類(lèi)中查找,然后才是模板參數列表。
template<typename X>
class Base {
public:
int basefield;
using T = int;
};
class D1: public Base<Base<void>> { // 實(shí)際上不是模板
public:
void f() { basefield = 3; } // 繼承成員普通訪(fǎng)問(wèn)
};
template<typename T>
class D2 : public Base<double> { // 繼承自非依賴(lài)型基類(lèi)
public:
void f() { basefield = 7; } // 繼承成員普通訪(fǎng)問(wèn)
T strange; // T 是 Base<double>::T 類(lèi)型,也就是int, 而不是模板參數T!
};
1.模板被實(shí)例化時(shí),編譯器需要知道實(shí)例化部分的完整定義。
1.模板實(shí)例化存在延遲現象,編譯器只會(huì )實(shí)例化需要的部分。如類(lèi)模板會(huì )只實(shí)例化用到的部分成員函數,函數模板如果提供了默認參數,也只會(huì )在這個(gè)參數會(huì )用到的時(shí)候實(shí)例化它。
1.兩階段查找:編譯器在模板解析階段會(huì )檢測不依賴(lài)于模板參數的非依懶型名稱(chēng),在模板實(shí)例化階段再檢查依懶型名稱(chēng)。
2.Points of Instantiation: 編譯器會(huì )在需要實(shí)例化模板的地方插入實(shí)例化點(diǎn)(POI)
1.函數模板實(shí)例化過(guò)程中,編譯器會(huì )根據實(shí)參的類(lèi)型和模板參數 T 定義的形式,推導出函數的各個(gè)參數的類(lèi)型,如果最后推導的結論矛盾,則推導失敗。
template<typename T>
T max (T a, T b)
{
return b < a ? a : b;
}
auto g = max(1, 1.0); // error 根據1推導T為int 根據1.0推導T為double
template<typename T> void f(T);
template<typename T> void g(T&);
double arr[20];
int const seven = 7;
f(arr); // T 被decay為double*
g(arr); //T 推斷為 double[20]
f(seven); // T 被decay為int
g(seven); // T 推斷為 int const
1.函數模板被取地址賦予函數指針時(shí),也會(huì )產(chǎn)生實(shí)參推導。
2.類(lèi)中定義了類(lèi)型轉換的模板函數時(shí),在類(lèi)型轉換時(shí)可以產(chǎn)生實(shí)參推導。
template<typename T>
void f(T, T);
void (*pf)(char, char) = &f; // T 被推斷為char
class S {
public:
template<typename T> operator T&();
};
void f(int (&)[20]);
void g(S s)
{
f(s); // 類(lèi)型轉換時(shí)T被推導為int[20]
}
1.模板實(shí)參如果是初始化列表時(shí),無(wú)法直接完成模板參數類(lèi)型 T 的推導。若函數參數通過(guò) std::initializer_list 定義,則實(shí)參類(lèi)型需要一致。
template<typename T> void g(T p);
template<typename T> void f(std::initializer_list<T>);
int main() {
g({’a’, ’e’, ’i’, ’o’, ’u’, 42}); // ERROR: T deduced to both char and int
f({1, 2, 3}); // ERROR: cannot deduce T from a braced list
f({2, 3, 5, 7, 9}); // OK: T is deduced to int
}
1.引用折疊:只有兩個(gè)右值引用會(huì )被折疊為右值引用,其它情形都是左值引用

using RCI = int const&;
RCI volatile&& r = 42; // OK: r has type int const&
using RRI = int&&;
RRI const&& rr = 42; // OK: rr has type int&&
1.根據 SFINAE 原理,編譯器在用實(shí)參推導模板參數失敗時(shí),會(huì )將該模板忽略。
template<typename T, unsigned N>
T* begin(T (&array)[N])
{
return array;
}
template<typename Container>
typename Container::iterator begin(Container& c)
{
return c.begin();
}
int main()
{
std::vector<int> v;
int a[10];
::begin(v); // OK: only container begin() matches, because the first deduction fails
::begin(a); // OK: only array begin() matches, because the second substitution fails
}
1.函數模板和普通函數一樣,是可以被重載的。
#include<iostream>
template<typename T>
int f(T)
{
return 1;
}
template<typename T>
int f(T*)
{
return 2;
}
int main()
{
std::cout << f<int*>((int*)0) << std::endl; // 更匹配第一個(gè)函數calls f<T>(T)
std::cout << f<int>((int*)0) << std::endl; // 只匹配第二個(gè)函數 calls f<T>(T*)
std::cout << f((int*)0) << std::endl; // 都匹配,但第二個(gè)更特殊,優(yōu)先選用 calls f<T>(T*)
}
3.函數簽名由以下部分構成:
a. 非受限函數名稱(chēng)
b. 名稱(chēng)所屬的類(lèi)作用域
c. 函數的 const volatile 限定符
d. 函數參數的類(lèi)型
e. 如果是函數模板,還包括返回類(lèi)型、模板參數和模板實(shí)參。
// 以下模板的實(shí)例化函數可以同時(shí)存在
template<typename T1, typename T2>
void f1(T1, T2);
template<typename T1, typename T2>
void f1(T2, T1);
template<typename T>
long f2(T);
template<typename T>
char f2(T);
c++最開(kāi)始例子中最后一個(gè)函數實(shí)例化時(shí)和兩個(gè)模板均匹配,此時(shí)將選用更特殊的函數模板。
普通函數和模板函數也可以同時(shí)重載,此時(shí)在匹配程度相同時(shí),優(yōu)先調用普通函數。
template<typename T>
std::string f(T)
{
return 'Template';
}
std::string f(int&)
{
return 'Nontemplate';
}
int main()
{
int x = 7;
std::cout << f(x) << std::endl; // 調用非模板函數
}
重載只適用于函數模板,對于類(lèi)模板,可以使用特化的方式使得編譯器進(jìn)行更優(yōu)的選擇。
類(lèi)模板若提供了默認參數,特化后的模板可以不加。
template<typename T>
class Types {
public:
using I = int;
};
template<typename T, typename U = typename Types<T>::I>
class S; // 模板1聲明
template<>
class S<void> { // 特化后的模板2定義,可以不用默認參數
public:
void f();
};
template<> class S<char, char>; // 特化后的模板3聲明
template<> class S<char, 0>; // error 特化失敗,0不能當類(lèi)型
int main()
{
S<int>* pi; // OK: 使用模板1且不需定義
S<int> e1; // ERROR: 使用模板1且需要定義
S<void>* pv; // OK: 使用模板2且不需定義
S<void,int> sv; // OK: 使用模板2且不需定義
S<void,char> e2; // ERROR: 使用模板1且需要定義
S<char,char> e3; // ERROR: 使用模板3且需要定義
}
template<>
class S<char, char> { // 特化后的模板3定義,此處定義對main中的實(shí)例化調用是不可見(jiàn)的
};
template<typename T,typename U>
class Invalid {
};
Invalid<double, int> x1; // 實(shí)例化一個(gè)類(lèi)模板
template<typename T>
class Invalid<T,double>; // ok, 部分偏特化可以和實(shí)體化實(shí)體同時(shí)存在
template<>
class Invalid<double,int>; // ERROR: 全特化后的模板不能和實(shí)例化實(shí)體同時(shí)存在
template<typename T>
class S;
template<> class S<char**> {
public:
void print() const;
};
// 不能出現template<>
void S<char**>::print() const
{
std::cout << 'pointer to pointer to char\n';
}
template<typename T>
int f(T) // #1
{
return 1;
}
template<typename T>
int f(T*) // #2
{
return 2;
}
template<> int f(int) // OK: specialization of #1
{
return 3;
}
template<> int f(int*) // OK: specialization of #2
{
return 4;
}
template<typename T>
int f(T, T x = 42)
{
return x;
}
template<> int f(int, int = 35) // 特化時(shí)不能指定默認參數
{
return 0;
}
template<typename T>
int g(T, T x = 42)
{
return x;
}
template<> int g(int, int y)
{
return y/2; // 返回21
}
template<typename T> constexpr std::size_t SZ = sizeof(T);
template<> constexpr std::size_t SZ<void> = 0;
template<typename T>
class Outer {
public:
static int code;
void print() const {
std::cout << 'generic';
}
void test(){};
};
template<typename T>
int Outer<T>::code = 6; // 類(lèi)外定義
// 對于Outer<void>類(lèi)型,code和print將采用特化版本,其余采用普通版本
template<>
int Outer<void>::code = 12; // 特化靜態(tài)成員
template<>
void Outer<void>::print() const // 特化類(lèi)模板
{
std::cout << 'Outer<void>';
}
template<typename T, int I = 3>
class S; // primary template
template<typename T>
class S<int, T>; // ERROR: 參數類(lèi)型不匹配
template<typename T = int>
class S<T, 10>; // ERROR: 不能有默認參數
template<int I>
class S<int, I*2>; // ERROR:不能有非類(lèi)型表達式
template<typename U, int K>
class S<U, K>; // ERROR:特化模板和基本模板無(wú)區別
template<typename T> constexpr std::size_t SZ = sizeof(T);
template<typename T> constexpr std::size_t SZ<T&> = sizeof(void*);


template<typename T>
T accum (T const* beg, T const* end)
{
T total{}; // 初始化為零值
while (beg != end) {
total += *beg;
++beg;
}
return total;
}
int main() {
int num[] = { 1, 2, 3, 4, 5 };
std::cout << 'the average value of the integer values is '
<< accum(num, num+5) / 5; // 輸出3
char name[] = 'templates';
int length = sizeof(name)-1;
std::cout << 'the average value of the characters in \''
<< name << '\' is '
<< accum(name, name+length) / length;
// 輸出-5 因為字符轉為對應的八位ASCII編碼數字進(jìn)行累加,累計超過(guò)了char表示的最大數字
}
template<typename T>
struct AccumulationTraits;
template<>
struct AccumulationTraits<char> {
using AccT = int;
};
template<>
struct AccumulationTraits<short> {
using AccT = int;
};
template<>
struct AccumulationTraits<int> {
using AccT = long;
};
template<>
struct AccumulationTraits<unsigned int> {
using AccT = unsigned long;
};
template<>
struct AccumulationTraits<float> {
using AccT = double;
};
template<typename T>
auto accum (T const* beg, T const* end)
{
using AccT = typename AccumulationTraits<T>::AccT;
AccT total{}; // assume this actually creates a zero value
while (beg != end) {
total += *beg;
++beg;
}
return total;
}
template<>
struct AccumulationTraits<char> {
using AccT = int;
static AccT const zero = 0;
};
template<>
struct AccumulationTraits<short> {
using AccT = int;
static AccT const zero = 0;
};
template<>
struct AccumulationTraits<int> {
using AccT = long;
static AccT const zero = 0;
};
template<typename T>
auto accum (T const* beg, T const* end)
{
using AccT = typename AccumulationTraits<T>::AccT;
AccT total = AccumulationTraits<T>::zero;
while (beg != end) {
total += *beg;
++beg;
}
return total;
}
template<>
struct AccumulationTraits<char> {
using AccT = int;
static constexpr AccT zero() { // 通過(guò)內聯(lián)函數定義初始值
return 0;
}
};
template<typename T, typename AT = AccumulationTraits<T>>
auto accum (T const* beg, T const* end)
{
typename AT::AccT total = AT::zero();
while (beg != end) {
total += *beg;
++beg; }
return total;
}
// 求和策略
class SumPolicy {
public:
template<typename T1, typename T2>
static void accumulate (T1& total, T2 const& value) {
total += value;
}
};
// 求乘積策略
class MultPolicy {
public:
template<typename T1, typename T2>
static void accumulate (T1& total, T2 const& value) {
total *= value;
}
};
template<typename T,
typename Policy = SumPolicy,
typename Traits = AccumulationTraits<T>>
auto accum (T const* beg, T const* end)
{
using AccT = typename Traits::AccT;
AccT total = Traits::zero();
while (beg != end) {
Policy::accumulate(total, *beg);
++beg; }
return total;
}
#include<vector>
#include<list>
#include<iostream>
#include<typeinfo>
template<typename T>
struct ElementT; // primary template
template<typename T>
struct ElementT<std::vector<T>> { // partial specialization for std::vector
using Type = T;
};
template<typename T>
struct ElementT<std::list<T>> {
using Type = T;
};
// partial specialization for std::list
template<typename T, std::size_t N>
struct ElementT<T[N]> {
using Type = T;
};
template<typename T>
struct ElementT<T[]> {
using Type = T;
};
// 類(lèi)型函數,打印容器類(lèi)型T中元素的類(lèi)型
template<typename T>
void printElementType (T const& c)
{
std::cout << 'Container of '
<< typeid(typename ElementT<T>::Type).name()
<< ' elements.\n';
}
int main() {
std::vector<bool> s;
printElementType(s); // print b
int arr[42];
printElementType(arr); // print i
}
// 移除引用
template<typename T>
struct RemoveReferenceT {
using Type = T;
};
template<typename T>
struct RemoveReferenceT<T&> {
using Type = T;
};
template<typename T>
struct RemoveReferenceT<T&&> {
using Type = T;
};
// 添加引用
template<typename T>
struct AddLValueReferenceT {
using Type = T&;
};
template<typename T>
struct AddRValueReferenceT {
using Type = T&&;
};
// 判斷兩個(gè)類(lèi)型是否相同
template<typename T1, typename T2>
struct IsSameT {
static constexpr bool value = false;
};
template<typename T>
struct IsSameT<T, T> {
static constexpr bool value = true;
};
#include <iostream>
// 定義bool模板類(lèi)型
template<bool val>
struct BoolConstant {
using Type = BoolConstant<val>;
static constexpr bool value = val;
};
// 定義兩個(gè)實(shí)例化類(lèi)型表示true 和 false
using TrueType = BoolConstant<true>;
using FalseType = BoolConstant<false>;
// 普通IsSameT模板繼承FalseType
template<typename T1, typename T2>
struct IsSameT : FalseType
{
};
// 特化的相同類(lèi)型IsSameT模板繼承TrueType
template<typename T>
struct IsSameT<T, T> : TrueType
{
};
// 根據fool的結果調用不同的foolImpl
template<typename T>
void foolImpl(T, TrueType)
{
std::cout<<'true'<<std::endl;
}
template<typename T>
void foolImpl(T, FalseType)
{
std::cout<<'false'<<std::endl;
}
template<typename T>
void fool(T t)
{
foolImpl(t, IsSameT<T,int>{}); // IsSameT<T,int>{} 類(lèi)型可能為T(mén)rueType或FalseType
}
int main()
{
fool(5); // print true
fool(5.0); // print false
}
// 實(shí)現一個(gè)能判斷T是否具有默認構造函數的模板
template<typename T>
struct IsDefaultConstructibleT {
private:
// 定義兩個(gè)重載的函數模板,返回類(lèi)型不同,用于傳入IsSameT進(jìn)行判斷
template<typename U, typename = decltype(U())> // decltype(U()) 中U若不可以默認初始化,則替換失敗,這個(gè)函數模板被淘汰,注意此處不能為T(mén),否則會(huì )編譯失敗
static char test(void*);
template<typename>
static long test(...); // ...參數可以匹配任意實(shí)參
public:
// 將T傳入test,如可以默認構造,則匹配第一個(gè)test,test返回類(lèi)型為char,IsSameT 結果為true,否則為false
static constexpr bool value = IsSameT<decltype(test<T>(nullptr)), char>::value;
};
IsDefaultConstructibleT<int>::value //結果為 true
struct S {
S() = delete;
};
IsDefaultConstructibleT<S>::value // 結果為false
// helper to ignore any number of template parameters:
template<typename...> using VoidT = void;
// primary template:
template<typename, typename = VoidT<>>
struct IsDefaultConstructibleT : std::false_type
{
};
// partial specialization (may be SFINAE’d away):
template<typename T>
struct IsDefaultConstructibleT<T, VoidT<decltype(T())>> : std::true_type
{
};
1.可以基于 SFINAE 原理探測類(lèi)型 T 中是否含有名為 X 的成員。
// helper to ignore any number of template parameters:
template<typename...> using VoidT = void;
// primary template:
template<typename, typename = VoidT<>>
struct HasSizeTypeT : std::false_type
{
};
// 只有含有size_type時(shí)才會(huì )替換成功,其余情況偏特化的模板被忽略
template<typename T>
struct HasSizeTypeT<T, VoidT<typename T::size_type>> : std::true_type
{
};
std::cout << HasSizeTypeT<int>::value; // false
struct CX {
using size_type = std::size_t;
};
std::cout << HasSizeType<CX>::value; // true
2.對于 13.2 中的介紹的注入的類(lèi)名詞,上述檢查結果為 true。
struct size_type { };
struct Sizeable : size_type { };
std::cout << HasSizeTypeT<Sizeable>::value; //ture
#define DEFINE_HAS_TYPE(MemType) \
template<typename, typename = std::void_t<>> \
struct HasTypeT_##MemType \
: std::false_type { \
}; \
template<typename T> \
struct HasTypeT_##MemType<T, std::void_t<typename T::MemType>> \ : std::true_type { }
// 定義兩種類(lèi)型萃取
DEFINE_HAS_TYPE(value_type);
DEFINE_HAS_TYPE(size_type);
#define DEFINE_HAS_MEMBER(Member) \
template<typename, typename = std::void_t<>> \
struct HasMemberT_##Member \ : std::false_type { }; \
template<typename T> \
struct HasMemberT_##Member<T, std::void_t<decltype(&T::Member)>> \
: std::true_type { }
1.if-then-else:接收一個(gè)條件參數,根據條件從兩個(gè)類(lèi)型參數中做選擇。
template<bool COND, typename TrueType, typename FalseType>
struct IfThenElseT {
using Type = TrueType;
};
// 模板偏特化
template<typename TrueType, typename FalseType> struct IfThenElseT<false, TrueType, FalseType> {
using Type = FalseType;
};
template<bool COND, typename TrueType, typename FalseType>
using IfThenElse = typename IfThenElseT<COND, TrueType, FalseType>::Type;
template<typename T> void swap(T& x, T& y)
{
T tmp(x);
x = y;
y = tmp;
}
// Array<T> 參數將優(yōu)先匹配下面的版本
template<typename T>
void swap(Array<T>& x, Array<T>& y) {
swap(x.ptr, y.ptr);
swap(x.len, y.len);
}
c++中對于空類(lèi),為保證類(lèi)對象有唯一地址,會(huì )為其插入一子節的內存。
空基類(lèi)優(yōu)化:在空類(lèi)作為基類(lèi)時(shí),如果為它不分配內存不會(huì )導致它存儲到其他同類(lèi)型對象或者子類(lèi)型對象的相同地址上,則可以不分配。
class Empty {
using Int = int;// type alias members don’t make a class nonempty
};
class EmptyToo : public Empty { };
class EmptyThree : public EmptyToo { };
int main()
{
// 輸出的三個(gè)類(lèi)大小相同
std::cout << 'sizeof(Empty): ' << sizeof(Empty) << ’\n’;
std::cout << 'sizeof(EmptyToo): ' << sizeof(EmptyToo) << ’\n’;
std::cout << 'sizeof(EmptyThree): ' << sizeof(EmptyThree) << ’\n’;
}

// 兩種crtp形式
template<typename Derived> class CuriousBase {
};
class Curious : public CuriousBase<Curious> {
};
template<typename Derived> class CuriousBase {
};
template<typename T>
class CuriousTemplate : public CuriousBase<CuriousTemplate<T>> {
};
1.值元編程:在編譯期通過(guò)模板基于輸入參數計算得到結果值。
// 計算平方根的模板
template<typename T>
constexpr T sqrt(T x)
{
if (x <= 1) {
return x;
}
T lo = 0, hi = x;
for (;;)
{
auto mid = (hi+lo)/2, midSquared = mid*mid;
if (lo+1 >= hi || midSquared == x) {
return mid;
}
if (midSquared < x) { lo = mid;
} else {
hi = mid;
}
}
}
sqrt(9); // 編譯期計算結果
2.類(lèi)型元編程:在編譯期通過(guò)模板基于輸入類(lèi)型得到輸出類(lèi)型。
// primary template: in general we yield the given type:
template<typename T> struct RemoveAllExtentsT {
using Type = T;
};
// partial specializations for array types (with and without bounds):
template<typename T, std::size_t SZ>
struct RemoveAllExtentsT<T[SZ]> {
using Type = typename RemoveAllExtentsT<T>::Type;
};
template<typename T>
struct RemoveAllExtentsT<T[]> {
using Type = typename RemoveAllExtentsT<T>::Type;
};
template<typename T>
using RemoveAllExtents = typename RemoveAllExtentsT<T>::Type;
// 計算向量的點(diǎn)乘
template<typename T, std::size_t N>
struct DotProductT {
static inline T result(T* a, T* b)
{
return *a * *b + DotProduct<T, N-1>::result(a+1,b+1);
}
};
template<typename T>
struct DotProductT<T, 0> {
static inline T result(T*, T*) {
return T{};
}
};
template<typename T, std::size_t N>
auto dotProduct(std::array<T, N> const& x, std::array<T, N> const& y)
{
return DotProductT<T, N>::result(x.begin(), y.begin());
}
參考資料:
1.c++ templates 第二版
2.c++ templates 第二版翻譯.知乎
聯(lián)系客服