title: advanced_metaprogramming_in_classic_c++_part1_prerequisites_chapter1_templates
date: 2017-06-20 10:21:47
tags:
典型c++高級元編程
第一部分 預備知識 #include <prerequisites>
//!個人有疑問的地方以及個人簡介會加//? //!
//!翻譯中可能會有不準的地方朴下,我是結合上下文猜的椎椰。不準就不準吧因為水平不到位
- 1.0 模板
編程是通過與其中的一臺機器交談來向計算機教授某些東西的過程共同語言阎毅。 越接近機器堤魁,就越不自然娜汁。每種語言都有自己的表現(xiàn)力。 對于任何給定的概念褪猛,各種語言的實現(xiàn)有不同的復雜度吭露。在匯編中,我們必須給予非常豐富的描述孝冒,(顯得不可讀)c++的優(yōu)勢就在于在足夠接近機器底層的同時又能有優(yōu)美的表達柬姚,C++允許程序員用不同的風格來表達相同的概念,并且更加自然庄涡。
接下來深入了解c++模板系統(tǒng)的細節(jié)量承。
下面是一個代碼塊
double x = sq(3.14);
sq是什么?
sq可以是宏
#define sq(x) ((x)*(x))
可以是一個函數(shù)
double sq(double x) {return x*x;}
可以是一個模板函數(shù)
template <typename T>
inline T sq(const T& x){ return x*x; }
一個類型(沒有實例)//函數(shù)對象
class sq
{
double s_;
public:
sq(double x):s_(x*x)
{}
operator double() const
{
return s_;
}
};
或者一個全局變量
class sq_t
{
public:
typedef double value_type;
value_tupe operator() (double x)
{
return x*x;
}
};
const sq_t sq = sq_t();
不管如何實現(xiàn)穴店,你看到后在腦海中肯定有一個實現(xiàn)撕捍,不過腦海中的實現(xiàn)和現(xiàn)實中的實現(xiàn)是不一樣的,如果sq是一個類泣洞,放在函數(shù)模板中可能會有錯誤的推導結果
template <typename T> void f(T x);
f(cos(3.14)); //實例化f<double>
f(sq(3.14)); //實例化f<sq>忧风?
不僅如此,你還要考慮到各種數(shù)據(jù)類型被sq平方時在實現(xiàn)上要盡可能的高效斜棚,不同的實現(xiàn)有不同的效果
std::vector<double> v;
std::transform(v.begin(),v.end(),v.begin(),sq);
速度瓶頸在sq的實現(xiàn)上阀蒂。(宏會報錯)
模板元編程TMP的意義在于
- 所見即所得,不必思考背后的實現(xiàn)
- 效率最高
- "自適應"http://?自洽弟蚀?,與其余程序融合酗失,可移植性強
“自適應”意味著移植性強义钉,不拘泥于編譯器實現(xiàn),不受約束规肴,sq數(shù)據(jù)從一個抽象基類中繼承出來可不滿足 自洽自適應捶闸。
c++模板真正強大的地方在于類型//不變量
考慮兩個表達式
double x1 = (-b + sqrt(b\*b - 4\*a\*c))/(2\*a);
double x2 = (-b + sqrt(sq(b) - 4\*a\*c))/(2\*a);
模板的參數(shù)計算與類型推導都在編譯期間完成夜畴,運行時不會有消耗。如果sq實現(xiàn)的好的話第二行要比第一行快且可讀性強一些
用sq更優(yōu)雅
- 代碼可讀性強/邏輯自證
- 沒有運行負擔
- 最佳優(yōu)化
事實上在模板基礎上可以輕松加上特化
template <>
inline double sq(const double & x)
{
//here ,use any special algorithm you have
}
- 1.1. C++ 模板
典型的c++模板删壮,函數(shù)模板和類模板//備注:當前c++還支持其他模板贪绘,但都可以看做它們的擴展
只要你提供了滿足條件的參數(shù),在編譯期間模板展開就可以了央碟。
一個函數(shù)模板可以推導函數(shù)税灌,一個類模板可以展開成類,TMP的要點可以總結為
- 你可以利用類模板在編譯期完成計算亿虽。
- 函數(shù)模板可以自動推導他們的參數(shù)類型
兩種模板都需要把參數(shù)列表放在尖括號里菱涤。可以是類型或者是整數(shù)和指針
//備注 理論上所有整數(shù)類型都可以洛勉,枚舉/bool/typedef/連編譯器提供的類型都支持(__int64 MSVC)
// 指向全局函數(shù)/成員函數(shù)的指針也允許粘秆,指向變量的指針可能會有限制。在Chapter 11會有討論
//!當然我能不能翻譯到哪里就不好說了
參數(shù)也可以有默認值//! 誰不知道啊
模板可以理解成一個元函數(shù)收毫,將參數(shù)映射成函數(shù)或類
比如 sq
template <typename T>
T sq(const T& x);
T -> T(*) (const T*)
同樣的攻走,類也是一個映射,比如 T -> basic_string<T>
通過類的模板偏特化來具化元函數(shù)此再,你可以有一個普通的模板和一堆偏特化陋气,它們有沒有內(nèi)容都可以。
定義的時候引润,類型就是形參巩趁,實例化的時候,類型就是實參淳附。
當你向模板提供了實參议慰, 作為元函數(shù)//!映射奴曙,模板就被實例化别凹,然后生成代碼,編譯器再將模板生成的代碼生成機器碼
要明白不同的實參產(chǎn)生不同的實例洽糟,即使形參看起來差不多炉菲,double和const double實例化的效果可是沒有相關性的。
當使用函數(shù)模板坤溃,編譯器會弄明白所有的形參拍霜,我們可以理解成形參綁定在模板形參上。
template <typename T>
T sq(const T& x) { return x*x; }
double pi = 3.14;
sq(pi); // the compiler "binds" double to T
double x = sq(3.14); // ok: the compiler deduces that T is double
double x = sq<double>(3.14); // this is legal, but less than ideal
所有的模板實參都是編譯期常數(shù)
- 類型形參可以接受任何類型//!只要是類型
- 非類型形參由最佳轉換原則自動推導
一個典型錯誤例子
template <int N>
class SomeClass{};
int main()
{
const int A = rand();
SomeClass<A> s; //error
static const int B = 2;
SomeClass<C> s; //fine
}
模板中常量寫法的最佳實踐是 static const [[integer type]] name = value;
//!指的應該不是現(xiàn)代c++,是classic C++
當然薪介,局部變量祠饺,static可以省略。不過這個并沒有什么壞處汁政,更清晰一些//!不必強求
傳遞到模板中的實參可以在編譯期計算出結果道偷,有效地整數(shù)運算都會在編譯期得到結果缀旁。
//!換句話說,無效的運算都會在編譯期被捕捉到而不是放在運行時崩一臉
- 除以0會導致編譯錯誤
- 禁止函數(shù)調(diào)用
- 生成代碼中的非整數(shù)/指針類型都是不可移植的勺鸦。//特指浮點型并巍,可以通過整型除法替代
SomeClass<(27+565) % 4> s1;
SomeClass<sizeof(void ) *CHAR_BIT>
除以0的錯誤的前提是模板整體都是常量
template <int N>
struct tricky
{
int f(int i =0)
{ return i/N;} //not a constant
};
int main()
{
tricky<0> t;
return t.f();
}
waring: potential divide by 0;
改成N/N ,是常數(shù)之后就會報錯了 實例化N為0或者0/0都會報錯
更確切一點换途,編譯期常量包括
整型字面量
sizeof懊渡,類似sizeof的得到整型結果的操作// alignof
非類型模板形參 在上下文中就是模板wrapper //!原文為"outer" template
template <int N>
class AnotherClass
{
SomeClass<N> _m;
};-
static 整型常數(shù)
template <int N,int K>
struct NK
{
static const int PRODUCT = N*K;
};SomeClass<NK<10,12>::PRODUCT > s1;
某些宏 LINE
SomeClass<LINE> s1;
//備注,一般沒人這么用怀跛,通常用來自動生成枚舉/實現(xiàn)斷言
模板形參可以依賴其他形參
template<typename T, int (*FUNC)(T)>
class X{};
template<typename T,T VALUE >
class Y{};
Y<int,7> y1;
Y<double,3> y2;//error 常數(shù)3沒有這種double類型
類(模板類)也可以有模板成員函數(shù)
struct math
{
template <typename T>
T sq(T x) const {return x*x;}
};
template <typename T>
struct _math{
template <typename _T>//備注 T和_T區(qū)分避免被外面的給掩了
static T product(T x, _T y){return x*y;}
};
double A = math().sq(3.14);
double B = _math<double>().product(3.14,5);
- 1.1.1 Typename
這個關鍵字用來
- 聲明模板形參距贷,替代class歧義
- 告訴編譯器如果不能識別出來,那它就是類型名
舉一個編譯器不識別的例子
template<typename T>
struct MyClass{
typedef double Y;
typedef T Type;
};
template<>
struct MyClass<int>{
static const int Y = 314;
typedef int Type;
};
int Q = 8;
template <typename T>
void SomeFunc(){
MyClass<T>::Y * Q;
// 這行代表一個Q的指向double的指針吻谋?還是314乘8忠蝗?
}
Y是依賴名字,因為它依賴一個未知的參數(shù)T
直接或間接的依賴于一個位置的參數(shù)的變量都是依賴名字漓拾,都應該明確的用typename說明
//阁最!這解決了我的一個疑問,之前遇到但是沒有深究骇两,我太菜了速种。見代碼和注釋
template <typename X>
class AnoterClass{
MyClass<X>::Type t1_;//error
typename MyClass<X>::Type t2_;//ok
MyClass<double>::Type t3_; //ok
};
要明白上面這個例子中,第一個必須有typename,第三個不能有typename
template <typename X>
class AnotherClass{
typename MyClass<X>::Y member1_;//ok 但是X是int不會編譯
typename MyClass<double>::Y member2_;//error
};
當聲明了一個沒有類型的模板形參時低千,需要typename引入依賴名字//!來推導出類型
template <typename T,typename T::type N>
struct SomeClass{};
struct S1{
typedef int type;
};
SomeClass<S1,3> x;
//!接下來這段不好翻譯
對于類型T1::T2如果實例化中發(fā)現(xiàn)是沒有類型的配阵,需要加上typename,等待后面實例化
直接上代碼
tmeplate <typename T>
struct B{
static const int N = sizeof(A<T>::X);
//應該是sizeof(typename A...)
};
直到實例化示血,B認為應該調(diào)用sizeof在沒有類型的參數(shù)上棋傍,當然啦sizeof也會自己推導出來,所以代碼沒錯难审,如果X是一個類型瘫拣,這個代碼也是合法的//!后面偏特化
見代碼
template <typename T>
struct A{
static const int X = 7;
};
template <>
struct A<char>{
typedef double X;
};
上面的例子沒有覆蓋一些陰暗的角落,有興趣請點擊這個陰暗的連接