欧美日韩电影精品视频_亚洲天堂一区二区三区四区_亚洲欧美日韩国产综合_日韩精品一区二区三区中文_為您提供優質色综合久久88色综合天天

您的位置:首頁 > 宏觀 >

C++98元編程技術(shù)解析

2023-09-08 17:55:19 來源:CppMore

評論

人們往往會將一個大問題拆解成許多小問題,通過解決一個個小問題,最終就能解決整個大問題。

若是拆解過后,這些小問題的處理邏輯不變,變化的只是輸入狀態(tài),那么此時就是一種代碼復(fù)用。對應(yīng)編程世界,一種是自上而下的拆解組合方式,稱為遞歸;一種是自下而上的拆解組合方式,稱為迭代。

拆解后還能組合,拆解才有意義,遞歸和迭代本身就帶有一種約束,必須具備起始狀態(tài)和終止?fàn)顟B(tài)。若是沒有起始狀態(tài),遞歸就沒有起點,循環(huán)就沒有開始;若是沒有終止?fàn)顟B(tài),遞歸就沒有終點,循環(huán)就沒有結(jié)束,


(資料圖)

本文所說的元編程拆解技術(shù),就是編譯期的問題拆解與組合技術(shù),編譯期不像運行期那樣能夠動態(tài)地改變輸入與輸出狀態(tài),C++誕生了許多技術(shù)來解決這個問題,這便是后文要介紹的。

本文以一個需求為例,來講解這些技術(shù):

要求編寫一個 unroll(callback) 函數(shù),該函數(shù)將調(diào)用 N 次傳入的 callback 函數(shù)。

起始狀態(tài)就是 N,終止?fàn)顟B(tài)就是 N 為零,拆解后處理邏輯不變的小問題就是 callback。

先不看文,你首先想到的是什么解法?文讀畢,對比一下文中的各種拆解技術(shù)思路,獲益更多。

原始遞歸法

模板元編程最開始就只支持遞歸這一種拆解方式,每次輸入一個狀態(tài),依次產(chǎn)生下一個狀態(tài),若狀態(tài)與遞歸終止?fàn)顟B(tài)相同,則結(jié)束。

采用這種方式,實現(xiàn)需求如下:

1namespacecpp98{ 2 3//declaration 4templatevoidunroll(F); 5 6template7structunroll_helper{ 8voidoperator()(Ff){ 9f();10unroll(f);11}12};1314//terminatedstate15template16structunroll_helper<0,F>{17voidoperator()(F){}18};1920//definition21template22voidunroll(Ff){23unroll_helper()(f);24}2526voidprint_cpp98(){27std::puts("hellocpp98");28}29}3031intmain(){32cpp98::unroll<2>(cpp98::print_cpp98);33//output:34//hellocpp9835//hellocpp9836}

由于函數(shù)模板不支持偏特化,于是需要借助一個 unroll_helper 類模板,來實現(xiàn)遞歸終止條件,再在 unroll 函數(shù)中調(diào)用該幫助類,不斷拆解問題。

遞歸輸入條件 N 為起始狀態(tài),每次拆解執(zhí)行完成之后,通過 N - 1 得到下一個狀態(tài),從而不斷解決問題。

這個時期,C++ 的元編程拆解技術(shù)還很弱,一個簡單的需求,實現(xiàn)起來也頗為繁瑣。

可變參數(shù)模板

時間來到 C++11,模板元編程迎來強大擴展,非常有用的一個新特性就是可變參數(shù)模板,它能夠讓我們擺脫遞歸這種原始拆解方式。

但是 C++11 還只是開始,基礎(chǔ)組件不完善,所以并不能非常有效地實現(xiàn)目標(biāo)。

什么意思呢?看如下這個不太完善的實現(xiàn):

1namespacecpp11{ 2 3template4classindex_sequence{}; 5 6template7voidunroll(Ff,index_sequence){ 8usingexpand=std::size_t[]; 9expand{(f(),Is)...};10}1112}131415intmain(){16cpp11::unroll([]{std::puts("hellocpp11");},17cpp11::index_sequence<2,1>());18}

原始遞歸法是采用不斷遞歸來動態(tài)地產(chǎn)生狀態(tài),而有了可變參數(shù)模板,狀態(tài)可以直接在編譯期初期產(chǎn)生,從而直接拿來用就可以。

這里定義了一個 index_sequence 用來接收所有狀態(tài),然后借助一些逗號表達式技巧展開參數(shù)包,在參數(shù)包展開的過程當(dāng)中,執(zhí)行處理邏輯。

C++11 起也支持 Lambda,因此也不用再提供一個額外的調(diào)用函數(shù)。

這個實現(xiàn)的唯一缺點就是由于缺乏相應(yīng)的組件,需要手動產(chǎn)生狀態(tài),導(dǎo)致使用起來較為麻煩。

完善版可變參數(shù)模板

C++14 增加了 std::index_sequence 和 std::make_index_sequence,于是就能將手動產(chǎn)生狀態(tài)變成動態(tài)產(chǎn)生,完善實現(xiàn)。

代碼如下:

1namespacecpp14{ 2 3template4voidhelper(Ff,std::index_sequence){ 5usingexpand=std::size_t[]; 6expand{(f(),Is)...}; 7} 8 9//variabletemplate10template11autounroll=[](autof){//genericlambda12helper(f,std::make_index_sequence{});13};14}1516intmain(){17cpp14::unroll<3>([]{std::puts("hellocpp14");});18}

同時,C++14 還支持 variable template 和 generic lambda,這進一步簡化了實現(xiàn)。

Fold Expression

前面的方式是采用逗號表達式技巧來展開參數(shù)包,C++17 支持 Fold expression,可以直接展開,因此代碼得到進一步簡化。

變成:

1namespacecpp17{ 2 3template4voidhelper(Ff,std::index_sequence){ 5((f(),Is),...);//foldexpression 6} 7 8template9autounroll=[](autof){//genericlambda10helper(f,std::make_index_sequence{});11};12}

constexpr if

C++17 的另一種拆解技術(shù)是借助 constexpr if,它的好處在于能夠直接在本函數(shù)內(nèi)判斷終止?fàn)顟B(tài),這樣就不再需要去定義一個遞歸終止函數(shù)。

1namespacecpp17{ 2//variabletemplate+constexprif 3template4autounroll=[](autoexpr){ 5ifconstexpr(N){ 6expr(); 7unroll(expr); 8} 9};10}1112intmain(){13cpp17::unroll<3>([]{std::puts("hellocpp17");});14}

與原始遞歸法相比,這種方式除了消除遞歸終止函數(shù),還免于編寫一個額外的 helper 類,generic lambda 更是減少了模板參數(shù)。

這是目前為止,最簡潔的實現(xiàn)。

C++20 雙層 Lambda 法

有沒有非遞歸的簡潔拆解方式呢?當(dāng)然也有。

看如下實現(xiàn):

1namespacecpp20{ 2 3templateconstexprautounroll=[](autof){ 4[f](std::index_sequence){ 5((f(),void(Is)),...); 6}(std::make_index_sequence()); 7}; 8} 910intmain(){11cpp20::unroll<3>([]{std::puts("hellocpp20");});12}

這里的關(guān)鍵是 C++20 的 template lambda,它支持為 lambda 編寫模板參數(shù),基于此才能夠編寫索引的模板參數(shù)。

Lambda 函數(shù)里面再套一個 Lambda 函數(shù),外層用于提供調(diào)用接口,內(nèi)層用于管理狀態(tài)和處理調(diào)用。如果沒有 template lambda,內(nèi)層 Lambda 的 std::index_sequence 參數(shù)就無法寫,也就接收不了狀態(tài)。、

Structured Binding Packs

原本有些新特性是應(yīng)該在 C++23 就進入標(biāo)準(zhǔn)的,但由于種種原因,我們只有期望 C++26 能用上了。Structured binding packs 就是這么一個特性。

前面除了遞歸以外的所有拆解方法,都得借助 std::index_sequence,這就是代碼依舊復(fù)雜的原因所在。

有沒有一種方式可以直接讓我們訪問參數(shù)包,而不必再定義一個參數(shù)為 std::index_sequence 的函數(shù)才能拿到那些參數(shù)包?Structured binding packs 就提供了這一能力。

這是 P1061 所提出的一種方式,讓我們能夠通過 Structured bindings 直接得到參數(shù)包。

于是實現(xiàn)變?yōu)椋?/p>

1namespacep1061{ 2 3templateconstexprautounroll=[](autof){ 4auto[...Is]=std::make_index_sequence(); 5((f(),void(Is)),...); 6}; 7 8} 910intmain(){11p1061::unroll<3>([]{std::puts("hellop1061");});12}

這種拆解技術(shù)才是最直觀的方式,兩行代碼解決一切。

Expansion Statements

另外一種方式就是我們在反射中經(jīng)常使用到的一個特性:template for。

這種方式比 Structured Binding Packs 更強大,是靜態(tài)反射里面的一個擴展特性,能夠支持編譯期迭代。

對于本次需求的實現(xiàn)為:

1namespacep1306{ 2templateconstexprautounroll=[](autof){ 3constexprstd::arraydummy{}; 4templatefor(auto&e:dummy) 5f(); 6}; 7} 8 9intmain(){10p1306::unroll<3>([]{std::puts("hellop1306");});11}

這里借助了 std::array,構(gòu)建了一個并不會實際使用的變量,目的是為了當(dāng)作遍歷次數(shù)。

總結(jié)

本文從 C++98 開始介紹了許多拆解技術(shù),在不斷的優(yōu)化過程中,也能夠看到 C++ 的發(fā)展歷程。

由最原始的復(fù)雜、難用,到最后的兩行代碼搞定,也能夠看到 C++ 元編程的發(fā)展。

利用好這些技術(shù),對大家的元編程能力會有顯著提高。

審核編輯:湯梓紅

關(guān)鍵詞:

[責(zé)任編輯:]

相關(guān)閱讀