C++98元編程技術(shù)解析_第1頁
C++98元編程技術(shù)解析_第2頁
C++98元編程技術(shù)解析_第3頁
C++98元編程技術(shù)解析_第4頁
C++98元編程技術(shù)解析_第5頁
已閱讀5頁,還剩3頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第第頁C++98元編程技術(shù)解析人們往往會將一個大問題(拆解)成許多小問題,通過解決一個個小問題,最終就能解決整個大問題。

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

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

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

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

要求編寫一個unroll(callb(ac)k)函數(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é)束。

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

1namespace

cpp98

{23

//

declara(ti)on4

(te)mplate

void

unroll(F);56

template

7

struct

unroll_helper

{8

void

operator()(F

f)

{9

f();10

unroll(f);11

}12

};1314

//

terminated

state15

template

16

struct

unroll_helper

{17

void

operator()(F)

{}18

};1920

//

definition21

template

22

void

unroll(F

f)

{23

unroll_helper()(f);24

}2526

void

print_cpp98()

{27

std::puts("hello

cpp98");28

}29}3031int

main()

{32

cpp98::unroll(cpp98::print_cpp98);33

//

output:34

//

hello

cpp9835

//

hello

cpp9836}

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

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

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

可變參數(shù)模板

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

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

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

1namespace

cpp11

{23

template

4

class

index_sequence

{};56

template

7

void

unroll(F

f,

index_sequence)

{8

using

expand

=

std::size_t[];9

expand{

(f(),

Is)...

};10

}1112}131415int

main()

{16

cpp11::unroll([]

{

std::puts("hello

cpp11");

},17

cpp11::index_sequence());18}

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

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

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

這個實(shí)現(xiàn)的唯一缺點(diǎ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)生,完善實(shí)現(xiàn)。

代碼如下:

1namespace

cpp14

{23

template

4

void

helper(F

f,

std::index_sequence)

{5

using

expand

=

std::size_t[];6

expand{

(f(),

Is)...

};7

}89

//

variable

template10

template

11

auto

unroll

=

[](auto

f)

{

//

generic

lambda12

helper(f,

std::make_index_sequence{});13

};14}1516int

main()

{17

cpp14::unroll([]

{

std::puts("hello

cpp14");

});18}

同時(shí),C++14還支持variabletemplate和genericlambda,這進(jìn)一步簡化了實(shí)現(xiàn)。

FoldExpression

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

變成:

1namespace

cpp17

{23

template

4

void

helper(F

f,

std::index_sequence)

{5

((f(),

Is),

...);

//

fold

expression6

}78

template

9

auto

unroll

=

[](auto

f)

{

//

generic

lambda10

helper(f,

std::make_index_sequence{});11

};12}

constexprif

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

1namespace

cpp17

{2

//

variable

template

+

constexpr

if3

template

4

auto

unroll

=

[](auto

expr)

{5

if

constexpr

(N)

{6

expr();7

unroll(expr);8

}9

};10}1112int

main()

{13

cpp17::unroll([]

{

std::puts("hello

cpp17");

});14}

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

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

C++20雙層Lambda法

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

看如下實(shí)現(xiàn):

1namespace

cpp20

{23

template

constexpr

auto

unroll

=

[](auto

f)

{4

[f](std::index_sequence)

{5

((f(),

void(Is)),

...);6

}(std::make_index_sequence());7

};8}910int

main()

{11

cpp20::unroll([]

{

std::puts("hello

cpp20");

});12}

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

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

StructuredBindingPacks

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

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

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

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

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

1namespace

p1061

{23

template

constexpr

auto

unroll

=

[](auto

f)

{4

auto

[...

Is]

=

std::make_index_sequence();5

((f(),

void(Is)),

...);6

};78}910int

main()

{11

p1061::unroll([]

{

std::puts("hello

p1061");

});12}

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

ExpansionStatements

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

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

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

1namespace

p1306

{2

template

constexpr

auto

unroll

=

[](auto

f)

{3

constexpr

std::array

dummy{};4

template

for

(auto6

};7}89int

m

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論