• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Impossible is nothing  
              愛過知情重醉過知酒濃   花開花謝終是空   緣份不停留像春風來又走   女人如花花似夢
            公告
            日歷
            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567
            統計
            • 隨筆 - 8
            • 文章 - 91
            • 評論 - 16
            • 引用 - 0

            導航

            常用鏈接

            留言簿(4)

            隨筆分類(4)

            隨筆檔案(8)

            文章分類(77)

            文章檔案(91)

            相冊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

             

            C++ 的另一個新世界

            C++ 的 MetaProgramming

            廢話就不說了, 按照C的傳統慣例,介紹programming的最好方式就是show代碼, 第一個例子就是Hello,world, 這篇文章也不例外

            在任何一個cpp文件中,輸入

            struct hello_world; //forward declaration
            struct A : hello_world
            {
            };

            然后編譯..,注意我沒有說"編譯運行",而僅僅說的是"編譯", 如果不意外的話,在你編譯器的輸出窗口會出現
            base class hello_world undefined
            或者
            base class `hello_world' has incomplete type

            等類似的語句, 至少在你的屏幕上打印出了 "hello_world" 字樣,對吧? 對了,這就是這個例子的目的, 我不也打印出來的嗎?

            上面這個簡陋的例子闡述了一個重要的現象, "編譯時"而不是運行時, 這就是meta programming的世界, 一個處于編譯期間, 而不是運行期間的世界.

            運行時我們能作的事情很多在編譯時我們不能作, 例如我們不能調用函數, 我們不能創建對象, 我們也不能設置斷點, 一切都交給你的C++編譯器.


            接下來, 首先回顧一下一些C++的基本模板知識.
            由于C++的編譯器符合ISO 標準的程度不一, 我使用的是VC++ 6 和 gcc version 3.2.3 (mingw special 20030504-1)
            下面的例子我在這兩個編譯器中都測試過.

            C++模板的最經常的認識就是STL中的容器, 例如
            vector<int> 就是一個可以裝int的動態數組, vector<sharp*> 就是一個可以裝sharp對象指針的數組.

            然后我們還需要一點點模板特化的知識.
            例如

            template<class T> struct Foo {};

            這是一個通用模板, 必配任何類型, 如果我們需要對int進行特別處理, 那么

            template<> struct Foo<int>  {}; 

            這樣就實現了對 int 類型的完全特化 . VC6 只支持完全特化, 不支持偏特化, 或者部分特化.
            不過還是稍微介紹一下:
            什么是遍特化了? 還就上面這個例子而言, 如果我們想對所有的指針進行特化, 那么應該是
            template<class T> struct Foo<T*> {}

            那么什么又是部分特化了, 看看這個:
            template<class T, class U> struct bar {};

            template<class T> struct bar<T, int>  {};

            后面這個就是對U模板參數的部分特化,使得 U為 int的時候 使用這個版本.

            這篇文章由于針對VC6, 因此你不會遇到偏特化和部分特化的情況.


            hello,world的例子以后, 我們準備干點有意思的事情. 例如求兩數之和. 首先看看在運行時的函數我們應該如何實現:

            定義:
            int sum(int x, int y) { return x+y; }

            調用:
            int j =  sum(3, 4)


            那么如何用meta programming的方式實現了?

            meta programming 由于處于編譯器, 因此給它的參數必須是編譯時就可以確定的, 在當前C++中允許的為,integer 和 type.

            就上面這個例子, 對于模板的調用應該是:
            int j = sum_<3,4>::value;

            3,4 都為整形常量, 編譯時確定, 然后返回值如何取得? 記住在編譯時是無法進行真正的函數調用的,因此我們只有通過定義在模板類中的一個常量
            來獲得返回結果. 最簡單的方法就是在一個 struct定義一個匿名的enum .

            sum的定義如下:
            template<int x, int y>
            struct sum_
            {
              enum { value = x + y };
            };

            然后你可以編譯后"運行"檢查檢查看看運行結果

            #include <iostream>

            using namespace std;

            template<int x, int y>
            struct sum_

            {
               
                enum { value = x + y };

            };

            int main()

            {
               
               cout << sum_<3, 4>::value << endl;
               
               return 0;

            }


            上面之所以使用struct sum_而不是class sum_是因為struct的默認作用范圍上public, 可以省略幾個鍵擊.

            這個例子通用展示了一個重要的觀點, 對應通常可以在運行時調用的函數
            int foo(int a, int b)
            其對應的meta 實現為:
            foo<a, b>::value
            也就是我們的foo現在為一個struct name, 參數通過< > 中的模板投遞, 結果通過 ::value 獲得其中定義的一個匿名enum值.

            那么如何計算 3,4,5的和?
            你可能會想如下:
             sum_<3,4,5>::value

            但是c++不支持一個模板類使用不同的參數參數存在, 換一個方式,
            你如何在 int sum(int a, int b) 存在的情況下獲得3個數的和?
            我們可以這樣:
            sum( 3, sum(4, 5))
            有lisp 背景的可能發現這很符合他們的思考方式, 至少以我浮淺的emacs lisp知識, 不使用中間變量. 同樣,meta中你也可以這樣使用:

            sum_<   //開始參數
            sum_< 3 //第一個參數為3
            sum_< 3, sum_<   //第二個參數是另外一個表達式的結果
            sum_< 3, sum_<4, //這個表達式的第一個參數為4
            sum_< 3, sum_<4, 5> //這個表達式的第二個參數為5
            sum_< 3, sum_<4, 5>::value //通過::value獲得這個表達式的結果
            sum_< 3, sum_<4, 5>::value >::value //然后獲得整個表達式的結果

            ok, 就這么多, 看出來沒有, 再解釋一次, 將上面我們的
              cout << sum_<3, 4>::value << endl;
               

            中4的位置用一個sum_ 替換就得到了我們需要的三個數之和.


              cout << sum_<3, ?? >::value << endl;
               
             ===>
               ?? =  sum_<4, 5>::value

             ===> then
              cout << sum_<3, sum_<4, 5>::value >::value << endl;
               


            如果我需要算 2, 3, 4, 5之和呢?
            同樣簡單, 你將上面的3, 4 ,5 中的任何一個常量替換成對sum_ 進行一個調用就可以了.
            例如:

            out << sum_< ?? , sum_<4, 5>::value >::value << endl;
               

            ?? =  sum_< 2, 3 >::value

            合并后為

            cout << sum_< sum_<2, 3>::value , sum_<4, 5>::value >::value << endl;
            運行的結果為 14.


            這就是meta中最簡單的一個例子, 順序調用, 如果你看明白了同時覺得有點意思的話, 下來我們講講
            循環語句, 通常我們寫程序都避免遞歸, 因為容易造成堆棧和大腦溢出,但是在 meta 中必須使用遞歸的方式.

            下面看看一個計算"階乘"的例子, 其實這個才真正是meta 中的hello,world.

            先看后面, 我們調用的方式:

            cout << fac<5>::value ;  // 結果應該是 5 * 4 * 3 * 2 * 1 = 120;

            通過前面的知識, 你知道fac是一個template struct的名字, 有一個模板參數int, value是其中的一個匿名枚舉變量
            于是你可以毫不猶豫的寫下:

            template<int i>
            struct fac
            {
              enum { value = ??? };
            };

            但是在value的地方你卡住了, 如果根據 i 得到 value?

            讓我們將大腦從"編譯時世界"切換到"運行時的世界", 你如何寫一個通常的遞歸函數來計算階乘?

            int fac(int n)
            {
              if( n == 1)
                return 1;
              return n * fac(n-1);
            }

            注意 n == 1是一個遞歸退出條件,先不考慮n為1時的遞歸退出,
            其他情況下是將n 乘以 fac (n - 1). 有了前面的sum_ 知識, 你應該可以推出 value = ???
            中的??? 應該是
            n *       //n*
            fac       //調用下一個fac
            fac<n-1>  //參數為n-1
            fac<n-1>::value //獲得結果

            因此fac "函數"的模板實現就是

            template<int i>
            struct fac
            {
              enum { value = i * fac<i - 1>::value };
            };

            然后我們再考慮遞歸退出條件, 為1時value應該為1, 拿起C++中的特化模板武器來
            template<>
            struct fac<1>      //參數為1時的特化
            {
              enum { value = 1 };
            };

            這樣就將參數為 1 時的返回值設置為1, 結束遞歸.

            這樣,當你輸入
            cout << fac<5>::value << endl;
            時,編譯器會實例化
            template<>
            struct fac<5>
            {
              enum { value = 5 * fac<4>::value };
            };

            由于fac<5> 需要 fac<4> , 因此然后實例化 fac<4>, 同樣的原因, fac<3> , fac<2>, fac<1>

            到fac<1> , 編譯器發現了一個特化版本fac<1> 匹配, 在fac<1>中的value已經是一個常量, 不依賴其他的fac實例, 遞歸退出, 最后
            我們得到最終結果120 .


            easy, 對不?

            然后再介紹 if語句.
            還是上面的這個fac例子, 負數的階乘是沒有意義的,先不考慮數學問題,假設我們希望在這個情況下返回-1,如何實現?

            如果我們輸入fac<-2>::value , 那么編譯器會嘗試實例化fac<-3>, fac<-4>, ......
            你發現這是一個無限遞歸條件, 在運行時會造成你堆棧溢出, 由于我們在"編譯時世界", 取決于你編譯器, 最終總有結束的時候.

            例如VC6 報錯: fatal error C1202: recursive type or function dependency context too complex
            G++報錯     : template instantiation depth exceeds maximum of 500

            因此我們需要一個if判斷語句, 發現當模板參數 < 1的時候返回 -1.

            按照測試先行的原則, 我們可以預計
            fac<-1>::value == -1  是成立的.
            現在的問題是如何實現? 下次再說吧! 也給個機會折磨折磨你的大腦. 記住, 模板不僅僅可以通過enum返回整數, 還可以通過嵌套 typedef返回一個類型.

            上回說到一個fac的版本, 希望在負數的情況下返回-1, 而不是無限遞歸下去.
            還是按照我們的思維, 先寫個對應"運行時世界"的版本.

            int safe_fac(int n)
            {
              if( n < 1)
                 return -1;
              return fac(n);
            }

            這個if邏輯很簡單, 如果模板參數<1, 那么直接返回 -1, 否則 還是使用前面的fac那個版本.
            好, 轉換成我們的meta 版本.

            你想,用個 ?: 運算符不就解決了嗎?

            template<int n>
            struct safe_fac
            {
              enum { value =  (n < 1 ? -1 : fac<n>::value ) };
            };

            可惜不對,  ?= 只有在"運行時世界"才能使用. 那么 value 后面的???寫什么好呢?

            先輕松輕松, 寫一個if的meta 版本, 我敢保證你能看得懂.
            template< bool b , class T, class U>
            struct if_
            {
              typedef T type;
            };

            注意了, 如果以前我們提到的例如sum_, fac等meta functions(其實就是c++中的模板類, 稱之為meta function是因為它們就像是function)
            是通過一個 在enum中的value 返回整形的話, 上面剛剛的if_這個例子就展示了 meta中的另外一個武器, 通過typedef 一個type 返回一個類型.

            如果我們這樣調用
            if_<true, int, double>::type  的結果就是 int 類型, 注意是"類型", 不是對象.

            我們想在b為false的時候返回第二個類型U, 即:
            if_<false, int, double>::type 的返回結果是double

            那么還是很簡單, 部分特化 b 參數就可以了.即:
            template<class T, class U>
            struct if_<false, T, U>
            {
              typedef U type;
            };

            我最前面說了, VC6不支持部分特化, 但是別忘了計算機時間的一條公理:
            任何計算機問題都可以通過增加一層來解決. 大部分VC6中的模板的問題還是可以解決的. 如果你不使用VC6, 這部分就不用看了.

            VC6是支持全部特化的, 因此我們可以將true, false特化出來

            template<bool>
            struct if_help
            {
               ...
            };

            template<>
            struct if_help<false>
            {
               ...
            };

            這個在vc6中是支持的. 然后我們還需要兩個額外的類型參數T,U, 這可以通過嵌套類來實現. 即

            template<bool>
            struct if_help
            {
              template<class T, class U>
              struct In
              {
                 typedef T type;
              };
            };

            template<>
            struct if_help<false>
            {
              template<class T, class U>
              struct In
              {
                 typedef U type;
              };
            };

            然后我們真正的if_ "meta 函數"如下定義:

            template<bool b, class T, class U>
            struct if_
            {
               typedef if_help<b>::In<T, U>::type type;
            };

            先根據b的內容實例化一個對應的if_help, : if_help<b>
            然后給其中的In模板投遞T,U參數, ::In<T, U>
            然后通過type獲得其中的返回類型 ::type
            最后typedef type一下作為自己的返回類型, 這樣外部就可以通過
            if_<true, int, double>::type  獲得返回的類型了.

            上面if_ 的實現實際上要用幾個C++關鍵字修飾一下:
            typedef if_help<b>::In<T, U>::type type;
             ===>
            typedef typename if_help<b>::template In<T, U>::type type;

            為什么要加上typename 和 template, 這個解釋起來到是很費勁. 有空再說.

            好了, 從模板的語法世界中清醒過來, 現在你知道的是, 我們有了一個if_ 的meta函數, 接受3個參數bool b, class T, class U,
            如果b為true, 那么它的返回值 (通過 if_::type 返回) 就是T,
            如果b為false, 那么它的返回值 (通過 if_::type 返回) 就是U.

            前面我提過了, 參數是通過<>來傳遞的, 因此一個例子就是

            if_                          //函數名
            if_<true,                    //第一個參數, bool 型
            if_<true, int                //第二個參數, 類型
            if_<true, int, double        //第3個參數,  類型
            if_<true, int, doubble>      //右括號表示參數結束
            if_<true, int, double>::type //通過::type獲得返回結果, 不是value了, 當然這僅僅是一個命名慣例.

            因此上面的那個 if_<true, int, double>::type 返回的就是 int,
            在"運行時世界", 你可以如下使用:

            for( if_<true, int, double>::type i = 0; i < 10; i++) {
              cout << "line " << i << "\n";
            }

            等同于
            for( int i = 0; i < 10; i++) {
              cout << "line " << i << "\n";
            }

            當然對于這個例子這樣使用是"有病". 我們等會會用到if_來實現前面的  safe_fac 實現了.

            注意我說的返回值并不是前面sum_例子中的整形了, 這個時候返回一個類型. 類型不是實例對象, 這點我想你應該清楚.
            編譯時不可能返回對象, 因為要調用構造函數, 要確定對象地址, 我們還沒有進入到" 運行時世界" , 對吧?
            實際上meta programming 最重要的使用并不是前面我們提到過的sum_, fac這些, 因為畢竟拿個計算器算一下也花不了幾個時間.
            但是返回type就不同了.
            那么type可以是什么呢? 可以是int, double這樣的基本類型, 也可以我們前面的 sum_ 模板類等等.

            然后再看safe_fac的實現:
            先不考慮 <1 的情況, 那么value就應該是直接調用以前的 fac 函數

            template<int n>
            struct safe_fac
            {
              enum { value = fac<n>::value };
            };

            然后再使得 >= 1時才使用fac函數, 那么利用我們前面的if_, 先忽略語法錯誤, 那么可以如下

            enum
            {
            value = if_< n < 1,???,  fac<n> >::type::value
            };

            首先, n < 1 為真時返回一個類型???, 暫時我們還沒有實現, 為false時返回
            fac<n>類型, 然后通過::type獲得返回的類型,或者為???, 或者為fac<n>,
            然后通過::value得到這個類型的整數結果.

            那么 ??? 應該是什么呢? 當然不能直接是-1, 否則 -1::value 就是語法錯誤.

            因此我們可以定義一個簡單的"函數", 返回-1:

            struct return_neg1
            {
               enum { value = -1 };
            };

            如果我們需要返回-2怎么辦? 又定義一個return_neg2 "函數"? 干脆我們一勞永逸, 定義如下:

            template<int n>
            struct int_
            {
               enum { value = n };
            };

            這樣int_ 這個"函數"就是你給我什么, 我就返回什么. 不過int_是一個類型, 例如:
            通過如下調用 int_<3>::value  返回它的結果3.

            有了這個, 我們的代碼就如下:

            value = if_< n < 1, int_<-1>,  fac<n> >::type::value

            原理清楚了, 最終的版本就是:

            template<int n>
            struct safe_fac
            {
              enum { value = if_< n < 1, int_<-1>,  fac<n> >::type::value };
            };

            試試:
            cout << safe_fac<-1>::value 輸出 -1.

            循環(遞歸表示), 條件判斷, 順序執行都有了, 剩下的就看你自己了.

            --------------------------------------------------------------------------------
            Boost中的mpl (meta programming library) 提供了一個專門用于metaprogramming的library, 同時前面提到的if_, int_等等就
            是從mpl中拷貝來的, 當然我簡化了很多.

            Modern C++ Design 其中的Typelist將meta progamming的循環(就是遞歸)發揮得淋漓盡致, 在侯捷的網站上www.jjhou.com有免費的前4章可讀. Typelist在第三章.
            書中序言部分, Effective C++的作者Meyers說到, 如果第3章的typelists沒有讓你感到振奮, 那你一定是很沉悶.
            就我親身體驗, 我覺得Meyers可能是說到委婉了些:
            如果第3章的typelists沒有讓你感到振奮, 那1%的可能是你是很沉悶, 99%的可能是你沒有看懂. I did.
            GoF之一的 John Vlissides同樣提到了typelists這一章就值得本書的價格. I believe.

            另外, 我發現即使在這本讓人抓狂的天才之作中, 作者仍然使用了n個TYPELIST_n 這樣的預處理宏來處理多個type的情況, 但是在sourceforge
            下的Loki庫中我發現已經有了一個MakeTypelist"函數", 看來 meta programming 確實是, 啊...
            是不是作者當時都沒有預見到還能夠以C++編譯器內建的模板能力, 而不是依賴預處理宏來處理typelist.?

            又, mpl中有專門裝type的容器....

            posted on 2006-02-27 23:37 笑笑生 閱讀(477) 評論(0)  編輯 收藏 引用 所屬分類: C++語言
             
            Copyright © 笑笑生 Powered by: 博客園 模板提供:滬江博客
            日日噜噜夜夜狠狠久久丁香五月| 久久国产香蕉一区精品| 无码任你躁久久久久久老妇App| 色偷偷91久久综合噜噜噜噜| 久久精品成人欧美大片| 乱亲女H秽乱长久久久| 精品一区二区久久| 亚洲午夜福利精品久久| 国产精品美女久久久久久2018| 国产精品热久久毛片| 久久中文字幕人妻熟av女| 91精品国产91久久久久久蜜臀| 国产亚洲精品久久久久秋霞| 久久婷婷国产综合精品| 亚洲精品高清一二区久久| 国产一区二区精品久久| 伊人久久大香线蕉成人| 狠狠久久综合| 久久久久久a亚洲欧洲aⅴ| 精品久久亚洲中文无码| 久久精品国产WWW456C0M| 青青草国产精品久久| 久久99精品久久久久久久久久| 色偷偷88欧美精品久久久| 91久久精品电影| 99久久无码一区人妻a黑| 亚洲中文字幕无码久久精品1| 无码国内精品久久人妻麻豆按摩| 久久被窝电影亚洲爽爽爽| 久久精品国产亚洲av影院| 久久精品国产亚洲AV忘忧草18| 久久er国产精品免费观看8| 久久综合九色综合久99| 精品综合久久久久久888蜜芽| 久久久久亚洲AV无码永不| 亚洲国产另类久久久精品| 久久精品人人做人人爽电影| 国产亚洲精品久久久久秋霞| 久久亚洲精品国产精品婷婷| 久久国产亚洲精品| 久久综合九色综合网站|