c++高級元編程 第一部分 第一章 第一小節(jié)


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

這個關鍵字用來

  1. 聲明模板形參距贷,替代class歧義
  2. 告訴編譯器如果不能識別出來,那它就是類型名

舉一個編譯器不識別的例子

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;
};

上面的例子沒有覆蓋一些陰暗的角落,有興趣請點擊這個陰暗的連接

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末告喊,一起剝皮案震驚了整個濱河市麸拄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌黔姜,老刑警劉巖拢切,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異地淀,居然都是意外死亡失球,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進店門帮毁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來实苞,“玉大人,你說我怎么就攤上這事烈疚。” “怎么了爷肝?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長灯抛。 經(jīng)常有香客問我,道長对嚼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任纵竖,我火速辦了婚禮漠烧,結果婚禮上,老公的妹妹穿的比我還像新娘靡砌。我一直安慰自己已脓,他們只是感情好,可當我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布通殃。 她就那樣靜靜地躺著度液,像睡著了一般。 火紅的嫁衣襯著肌膚如雪画舌。 梳的紋絲不亂的頭發(fā)上堕担,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天,我揣著相機與錄音骗炉,去河邊找鬼照宝。 笑死,一個胖子當著我的面吹牛句葵,可吹牛的內(nèi)容都是我干的厕鹃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼乍丈,長吁一口氣:“原來是場噩夢啊……” “哼剂碴!你這毒婦竟也來了?” 一聲冷哼從身側響起轻专,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤忆矛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體催训,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡洽议,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了漫拭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亚兄。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖采驻,靈堂內(nèi)的尸體忽然破棺而出审胚,到底是詐尸還是另有隱情,我是刑警寧澤礼旅,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布膳叨,位于F島的核電站,受9級特大地震影響痘系,放射性物質(zhì)發(fā)生泄漏菲嘴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一碎浇、第九天 我趴在偏房一處隱蔽的房頂上張望临谱。 院中可真熱鬧,春花似錦奴璃、人聲如沸悉默。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雳旅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抵拘,已是汗流浹背型豁。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工迎变, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驼侠。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓倒源,卻偏偏與公主長得像,于是被迫代替她去往敵國和親相速。 傳聞我的和親對象是個殘疾皇子突诬,可洞房花燭夜當晚...
    茶點故事閱讀 45,442評論 2 359

推薦閱讀更多精彩內(nèi)容