2016年8月6日 星期六

c++ 11 variadic template

非人磨墨墨磨人
學習 c++ 多年, 還是不熟悉 template, 這次專注在 variadic template 只是因為最近迷上 recursive 的思考邏輯, 很可能是因為這陣子寫了 simple c interpreter, parse ebnf 本身就是一種 recursive 的行為, 我體會到 recursive 的強大, 進而想學習他。而最近的 functional programming 思考方式密集地被傳道, 雖然有很多名詞, 但其最根本的思想也是 recursive。我覺得 recursive 是可以好好投資的一個技巧, 我願意花時間在上頭。

而 c++ 11 variadic template 是一個很類似的東西, 他把 recursive 思考方式帶到了編譯階段, 不過得靠《冥想》去想像該 recursive 是怎麼展開的, 這太難了, 我試圖以反組譯來具象化編譯器的神祕行為。

variadic template 有 function 和 class 兩種, 這邊提到的是 variadic template function。

vt1.cpp
 1 #include <iostream>
 2 #include <chrono>
 3 #include <vector>
 4 #include <string>
 5 #include <typeinfo>
 6 
 7 using namespace std;
 8 
 9 void f1(int val)
10 {
11   cout << "last type: " << typeid(val).name() << ", val: " << val << endl;
12 }
13 
14 template<typename T, typename... Args>
15 void f1(T val, Args... args)
16 {
17   cout << "type: " << typeid(val).name() << ", val: " << val << endl;
18   return f1(args...);
19 }
20 
21 int main(int argc, char *argv[])
22 {
23 
24   f1("ab", 0, 1, 2);
25 #if 0
26   f1("ab", 0, 1);
29 #endif
30   return 0;
31 }

g++ -std=c++14 -g -m32 -fno-stack-protector -g -g -c vt1.cpp
g++ -std=c++14 -g -m32 -fno-stack-protector -g -o vt1 vt1.o

執行結果:
descent@u64:cpp11$ ./vt1 
type: PKc, val: ab
type: i, val: 0
type: i, val: 1
last type: i, val: 2

編譯器產生了以下四個函式:
183<f1(int)>: // 這個就是 vt1.cpp L9, 需要事先準備好給編譯器, 否則無法展開 variadic template 
294<void f1<char const*, int, int, int>(char const*, int, int, int)>:
339<void f1<int, int, int>(int, int, int)>:
383 08048974 <void f1<int, int>(int, int)>:

然後以下面的方式呼叫他們,
f1("ab", 0, 1, 2) call f1(0, 1, 2) call f1(1, 2) call f1(2)

f1("ab", 0, 1, 2); // 只印出第一個引數 "ab"
| - f1(0, 1, 2); // 只印出第一個引數 0
    | - f1(1, 2); // 只印出第一個引數 1
        | - f1(2); // 只印出第一個引數 0
每次的函式呼叫都只會處理第一個實際參數, 一直下去直到把所有引數處理完璧。

list 1. 是反組譯的結果, 我加入了一些註解, 有興趣的朋友可以看看, 不過其實知道 c++ 編譯器怎麼展開 variadic template function 已經足夠, 不一定要看 list 1. 的反組譯。

list 1. objdump -Cd vt1
  1 
  2 vt1:     file format elf32-i386
  3 
  4 
  5 Disassembly of section .init:
  6 

183 0804874b <f1(int)>:
184  804874b: 55                    push   %ebp
185  804874c: 89 e5                 mov    %esp,%ebp
186  804874e: 53                    push   %ebx
187  804874f: 83 ec 04              sub    $0x4,%esp
188  8048752: 83 ec 0c              sub    $0xc,%esp
189  8048755: 68 f0 a0 04 08        push   $0x804a0f0
190  804875a: e8 eb 00 00 00        call   804884a <std::type_info::name() const>
191  804875f: 83 c4 10              add    $0x10,%esp
192  8048762: 89 c3                 mov    %eax,%ebx
193  8048764: 83 ec 08              sub    $0x8,%esp
194  8048767: 68 81 8a 04 08        push   $0x8048a81
195  804876c: 68 40 a0 04 08        push   $0x804a040
196  8048771: e8 9a fe ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
197  8048776: 83 c4 10              add    $0x10,%esp
198  8048779: 83 ec 08              sub    $0x8,%esp
199  804877c: 53                    push   %ebx
200  804877d: 50                    push   %eax
201  804877e: e8 8d fe ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
202  8048783: 83 c4 10              add    $0x10,%esp
203  8048786: 83 ec 08              sub    $0x8,%esp
204  8048789: 68 8d 8a 04 08        push   $0x8048a8d
205  804878e: 50                    push   %eax
206  804878f: e8 7c fe ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
207  8048794: 83 c4 10              add    $0x10,%esp
208  8048797: 83 ec 08              sub    $0x8,%esp
209  804879a: ff 75 08              pushl  0x8(%ebp) // 2
210  804879d: 50                    push   %eax
211  804879e: e8 1d fe ff ff        call   80485c0 <std::ostream::operator<<(int)@plt>
212  80487a3: 83 c4 10              add    $0x10,%esp
213  80487a6: 83 ec 08              sub    $0x8,%esp
214  80487a9: 68 30 86 04 08        push   $0x8048630
215  80487ae: 50                    push   %eax
216  80487af: e8 6c fe ff ff        call   8048620 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt>
217  80487b4: 83 c4 10              add    $0x10,%esp
218  80487b7: 90                    nop
219  80487b8: 8b 5d fc              mov    -0x4(%ebp),%ebx
220  80487bb: c9                    leave  
221  80487bc: c3                    ret    
222 
223 080487bd <main>:
224  80487bd: 8d 4c 24 04           lea    0x4(%esp),%ecx
225  80487c1: 83 e4 f0              and    $0xfffffff0,%esp
226  80487c4: ff 71 fc              pushl  -0x4(%ecx)
227  80487c7: 55                    push   %ebp
228  80487c8: 89 e5                 mov    %esp,%ebp
229  80487ca: 51                    push   %ecx
230  80487cb: 83 ec 04              sub    $0x4,%esp
231  80487ce: 6a 02                 push   $0x2 // 2
232  80487d0: 6a 01                 push   $0x1 // 1
233  80487d2: 6a 00                 push   $0x0 // 0
234  80487d4: 68 95 8a 04 08        push   $0x8048a95 // "ab"
235  80487d9: e8 8f 00 00 00        call   804886d <void f1<char const*, int, int, int>(char const*, int, int, int)>
236  80487de: 83 c4 10              add    $0x10,%esp
237  80487e1: b8 00 00 00 00        mov    $0x0,%eax
238  80487e6: 8b 4d fc              mov    -0x4(%ebp),%ecx
239  80487e9: c9                    leave  
240  80487ea: 8d 61 fc              lea    -0x4(%ecx),%esp
241  80487ed: c3                    ret    
242 
276 
294 0804886d <void f1<char const*, int, int, int>(char const*, int, int, int)>:
295  804886d: 55                    push   %ebp
296  804886e: 89 e5                 mov    %esp,%ebp
297  8048870: 53                    push   %ebx
298  8048871: 83 ec 04              sub    $0x4,%esp
299  8048874: 83 ec 0c              sub    $0xc,%esp
300  8048877: 68 e0 a0 04 08        push   $0x804a0e0
301  804887c: e8 c9 ff ff ff        call   804884a <std::type_info::name() const>
302  8048881: 83 c4 10              add    $0x10,%esp
303  8048884: 89 c3                 mov    %eax,%ebx
304  8048886: 83 ec 08              sub    $0x8,%esp
305  8048889: 68 98 8a 04 08        push   $0x8048a98
306  804888e: 68 40 a0 04 08        push   $0x804a040
307  8048893: e8 78 fd ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
308  8048898: 83 c4 10              add    $0x10,%esp
309  804889b: 83 ec 08              sub    $0x8,%esp
310  804889e: 53                    push   %ebx
311  804889f: 50                    push   %eax
312  80488a0: e8 6b fd ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
313  80488a5: 83 c4 10              add    $0x10,%esp
314  80488a8: 83 ec 08              sub    $0x8,%esp
315  80488ab: 68 8d 8a 04 08        push   $0x8048a8d // ", val: "
316  80488b0: 50                    push   %eax // this (ostream)
317  80488b1: e8 5a fd ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
318  80488b6: 83 c4 10              add    $0x10,%esp
319  80488b9: 83 ec 08              sub    $0x8,%esp
320  80488bc: ff 75 08              pushl  0x8(%ebp) "ab"
321  80488bf: 50                    push   %eax // this (ostream)
322  80488c0: e8 4b fd ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
323  80488c5: 83 c4 10              add    $0x10,%esp
324  80488c8: 83 ec 08              sub    $0x8,%esp
325  80488cb: 68 30 86 04 08        push   $0x8048630 
326  80488d0: 50                    push   %eax 
327  80488d1: e8 4a fd ff ff        call   8048620 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt>
328  80488d6: 83 c4 10              add    $0x10,%esp
329  80488d9: 83 ec 04              sub    $0x4,%esp
330  80488dc: ff 75 14              pushl  0x14(%ebp) // 2
331  80488df: ff 75 10              pushl  0x10(%ebp) // 1
332  80488e2: ff 75 0c              pushl  0xc(%ebp)  // 0
333  80488e5: e8 08 00 00 00        call   80488f2 <void f1<int, int, int>(int, int, int)>
334  80488ea: 83 c4 10              add    $0x10,%esp
335  80488ed: 8b 5d fc              mov    -0x4(%ebp),%ebx
336  80488f0: c9                    leave  
337  80488f1: c3                    ret    
338 
339 080488f2 <void f1<int, int, int>(int, int, int)>:
340  80488f2: 55                    push   %ebp
341  80488f3: 89 e5                 mov    %esp,%ebp
342  80488f5: 53                    push   %ebx
343  80488f6: 83 ec 04              sub    $0x4,%esp
344  80488f9: 83 ec 0c              sub    $0xc,%esp
345  80488fc: 68 f0 a0 04 08        push   $0x804a0f0
346  8048901: e8 44 ff ff ff        call   804884a <std::type_info::name() const>
347  8048906: 83 c4 10              add    $0x10,%esp
348  8048909: 89 c3                 mov    %eax,%ebx
349  804890b: 83 ec 08              sub    $0x8,%esp
350  804890e: 68 98 8a 04 08        push   $0x8048a98
351  8048913: 68 40 a0 04 08        push   $0x804a040
352  8048918: e8 f3 fc ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
353  804891d: 83 c4 10              add    $0x10,%esp
354  8048920: 83 ec 08              sub    $0x8,%esp
355  8048923: 53                    push   %ebx
356  8048924: 50                    push   %eax
357  8048925: e8 e6 fc ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
358  804892a: 83 c4 10              add    $0x10,%esp
359  804892d: 83 ec 08              sub    $0x8,%esp
360  8048930: 68 8d 8a 04 08        push   $0x8048a8d
361  8048935: 50                    push   %eax
362  8048936: e8 d5 fc ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
363  804893b: 83 c4 10              add    $0x10,%esp
364  804893e: 83 ec 08              sub    $0x8,%esp
365  8048941: ff 75 08              pushl  0x8(%ebp) // 0
366  8048944: 50                    push   %eax // this (ostream)
367  8048945: e8 76 fc ff ff        call   80485c0 <std::ostream::operator<<(int)@plt>
368  804894a: 83 c4 10              add    $0x10,%esp
369  804894d: 83 ec 08              sub    $0x8,%esp
370  8048950: 68 30 86 04 08        push   $0x8048630
371  8048955: 50                    push   %eax
372  8048956: e8 c5 fc ff ff        call   8048620 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt>
373  804895b: 83 c4 10              add    $0x10,%esp
374  804895e: 83 ec 08              sub    $0x8,%esp
375  8048961: ff 75 10              pushl  0x10(%ebp) // 2
376  8048964: ff 75 0c              pushl  0xc(%ebp)  // 1
377  8048967: e8 08 00 00 00        call   8048974 <void f1<int, int>(int, int)>
378  804896c: 83 c4 10              add    $0x10,%esp
379  804896f: 8b 5d fc              mov    -0x4(%ebp),%ebx
380  8048972: c9                    leave  
381  8048973: c3                    ret    
382 
383 08048974 <void f1<int, int>(int, int)>:
384  8048974: 55                    push   %ebp
385  8048975: 89 e5                 mov    %esp,%ebp
386  8048977: 53                    push   %ebx
387  8048978: 83 ec 04              sub    $0x4,%esp
388  804897b: 83 ec 0c              sub    $0xc,%esp
389  804897e: 68 f0 a0 04 08        push   $0x804a0f0
390  8048983: e8 c2 fe ff ff        call   804884a <std::type_info::name() const>
391  8048988: 83 c4 10              add    $0x10,%esp
392  804898b: 89 c3                 mov    %eax,%ebx
393  804898d: 83 ec 08              sub    $0x8,%esp
394  8048990: 68 98 8a 04 08        push   $0x8048a98
395  8048995: 68 40 a0 04 08        push   $0x804a040
396  804899a: e8 71 fc ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
397  804899f: 83 c4 10              add    $0x10,%esp
398  80489a2: 83 ec 08              sub    $0x8,%esp
399  80489a5: 53                    push   %ebx
400  80489a6: 50                    push   %eax
401  80489a7: e8 64 fc ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
402  80489ac: 83 c4 10              add    $0x10,%esp
403  80489af: 83 ec 08              sub    $0x8,%esp
404  80489b2: 68 8d 8a 04 08        push   $0x8048a8d
405  80489b7: 50                    push   %eax
406  80489b8: e8 53 fc ff ff        call   8048610 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
407  80489bd: 83 c4 10              add    $0x10,%esp
408  80489c0: 83 ec 08              sub    $0x8,%esp
409  80489c3: ff 75 08              pushl  0x8(%ebp) // 1
410  80489c6: 50                    push   %eax
411  80489c7: e8 f4 fb ff ff        call   80485c0 <std::ostream::operator<<(int)@plt>
412  80489cc: 83 c4 10              add    $0x10,%esp
413  80489cf: 83 ec 08              sub    $0x8,%esp
414  80489d2: 68 30 86 04 08        push   $0x8048630
415  80489d7: 50                    push   %eax
416  80489d8: e8 43 fc ff ff        call   8048620 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt>
417  80489dd: 83 c4 10              add    $0x10,%esp
418  80489e0: 83 ec 0c              sub    $0xc,%esp
419  80489e3: ff 75 0c              pushl  0xc(%ebp) // 2
420  80489e6: e8 60 fd ff ff        call   804874b <f1(int)>
421  80489eb: 83 c4 10              add    $0x10,%esp
422  80489ee: 8b 5d fc              mov    -0x4(%ebp),%ebx
423  80489f1: c9                    leave  
424  80489f2: c3                    ret    

辛苦看懂了反組譯後的程式碼, 再來要問, 這應該要怎麼用呢? 我應該投資時間在這種技巧上嗎?

和 move semantics 不同, 這次我沒有答案, 我不知道如何在我的程式碼上用上這種技巧, 我也不想為了用這種技巧而用, 我只是想寫出容易理解的程式, 並不想耍什麼花招。

而且在看過反組譯之後應該會發現程式碼大幅度的增加, 隨著引數愈多, 產生出的函式愈多, 程式碼會變得龐大。c++ 總是強調其效率, 卻從不提這是以程式碼變大的代價換來的。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。