Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Lambda in template_final

3,724 views

Published on

  • Be the first to comment

Lambda in template_final

  1. 1. C++11 のラムダ式は なぜ関数テンプレートの戻り値 型やパラメタ型に現れることが できないのか? 2013/11/02 Cryolite C++ 例外安全 Day 1
  2. 2. そもそも Q. なぜラムダ式を関数テンプ レートの戻り値型やパラメタ 型に置きたいのか? 2
  3. 3. そもそも Q. なぜラムダ式を関数テンプ レートの戻り値型やパラメタ 型に置きたいのか? A. C++ メタプログラミングに おいて死活問題だから. 3
  4. 4. 例題: std::is_constructible を実装しなさい 4
  5. 5. 例題: std::is_constructible を実装しなさい struct is_constructible<T, Args…>: template<class T> typename add_rvalue_reference<T>::type create(); T t(create<Args>()...); がコンパイルできるかどうかを調べる. 5
  6. 6. 例題: std::is_constructible を実装しなさい struct is_constructible<T, Args…>: template<class T> typename add_rvalue_reference<T>::type create(); T t(create<Args>()...); がコンパイルできるかどうかを調べる. Args... という型を持つ変数で型 T の コンストラクタを呼べるかどうか, をコンパイル時 bool 値として取り出す. 6
  7. 7. 答え (ラムダ式の出現に制限があ るとき): 実際に GCC (libstdc++) 4.8.2 の 実装を見てみましょう 7
  8. 8. 答え (ラムダ式の出現に制限がな いとき): template<class T, class... Args> constexpr decltype([] { T t(create<Args>()...); }, true) test(int) { return true; } template<class T> constexpr bool test(bool) { return false; } 8
  9. 9. ここまでのまとめ: 9
  10. 10. ここまでのまとめ: ラムダ式が関数テンプレートの戻 り値型やパラメタ型になれるかな れないかは, C++ メタプログラミ ングにおいて死活問題. 10
  11. 11. ところが C++11 では ラムダ式の出現に制限がある! 11
  12. 12. ところが C++11 では ラムダ式の出現に制限がある! (※ラムダ式は constexpr ではない + ラムダ式は unevaluated operands に出現 できない) 12
  13. 13. ところが C++11 では ラムダ式の出現に制限がある! (※ラムダ式は constexpr ではない + ラムダ式は unevaluated operands に出現 できない) 何か理由があるはず! 13
  14. 14. C++11 でラムダ式の出現に 制限がある理由: 14
  15. 15. C++11 でラムダ式の出現に 制限がある理由: マングリングがしんどいから 15
  16. 16. C++11 でラムダ式の出現に 制限がある理由: マングリングがしんどいから ……え? 16
  17. 17. C++11 における関数 テンプレートのマングリング 2013/11/02 Cryolite C++ 例外安全 Day 17
  18. 18. マングリングの例 void f(int) { ..... } 18
  19. 19. マングリングの例 void f(int) { $ ..... $ } g++ -std=c++ -c main.cpp nm main.o 0000000000000000 T _Z1fi $ c++filt _Z1fi f(int) 19
  20. 20. マングリングの例 void f(int) { ..... } void f(double) { ..... } 20
  21. 21. マングリングの例 void f(int) { ..... } $ g++ -std=c++ -c main.cpp $ nm main.o 0000000000000009 T _Z1fd 0000000000000000 T _Z1fi void f(double) $ c++filt _Z1fi { f(int) ..... $ c++filt _Z1fd } f(double) 21
  22. 22. マングリングは 22
  23. 23. マングリングは あるプログラム中に 共存することが許されている 異なるエンティティを 区別できないといけない 23
  24. 24. マングリングの例 // a.cpp void f(int) { ..... } // b.cpp int f(int) { ..... } 24
  25. 25. マングリングの例 $ g++ -std=c++11 -c a.cpp // a.cpp void f(int) $ nm a.o 0000000000000000 T _Z1fi { $ c++filt _Z1fi ..... f(int) } // b.cpp int f(int) { ..... } $ g++ -std=c++11 -c a.cpp $ nm a.o 0000000000000000 T _Z1fi $ c++filt _Z1fi f(int) 25
  26. 26. マングリングの例 $ g++ -std=c++11 -c a.cpp // a.cpp void f(int) $ nm a.o 0000000000000000 T a.o b.o $ g++ -std=c++11 -c _Z1fi { $ c++filt _Z1fi ..... (リンク時に定義の異なる関数に対す f(int) } るシンボルが衝突するので何が起きる か分からない) $ g++ -std=c++11 -c a.cpp // b.cpp が,元々 well-defined なプログラム $ nm a.o int f(int) じゃないのでそんなことは知ったこっ 0000000000000000 T _Z1fi { ちゃない! $ c++filt _Z1fi ..... f(int) } 26
  27. 27. マングリングは あるプログラム中に 共存することが許されていない エンティティ同士を 区別できる必要はない 27
  28. 28. まとめ:マングリングは あるプログラム中に 共存することが許されている 異なるエンティティを 区別できないといけない あるプログラム中に 共存することが許されていない エンティティ同士を 区別できる必要はない 28
  29. 29. 関数テンプレートの特殊化 に対するマングリングの例 template<typename T> void f(int) {} int main() { f<int>(0); } 29
  30. 30. 関数テンプレートの特殊化 に対するマングリングの例 template<typename T> void f(int) {} $ g++ -std=c++11 -c main.cpp $ nm main.o int main() 0000000000000000 W _Z1fIiEvi { f<int>(0);$ c++filt _Z1fIiEvi void f<int>(int) } 30
  31. 31. template<typename T> auto add(T const &x, T const &y) 関数テンプレートの特殊化 -> decltype(x + y) {に対するマングリングの例 return x + y; } struct X { X operator+(X const &rhs) const { ..... } }; int main() { X x, y; add(x, y); } 31
  32. 32. template<typename T> auto add(T const &x, T const &y) 関数テンプレートの特殊化 -> decltype(x + y) {に対するマングリングの例 return x + y; } struct X { X g++ -std=c++11const &rhs) const $ operator+(X -c main.cpp { nm main.o $ ..... } }; 0000000000000000 W _Z3addI1XEDTplfp_fp0_ERKT_S4_ int main() { X x, y; add(x, y); } 32
  33. 33. template<typename T> auto add(T const &x, T const &y) 関数テンプレートの特殊化 -> decltype(x + y) {に対するマングリングの例 return x + y; } struct X { X g++ -std=c++11const &rhs) const $ operator+(X -c main.cpp { nm main.o $ ..... } }; 0000000000000000 W _Z3addI1XEDTplfp_fp0_ERKT_S4_ $ c++filt _Z3addI1XEDTplfp_fp0_ERKT_S4_ int main() { X x, y; add(x, y); } 33
  34. 34. template<typename T> auto add(T const &x, T const &y) 関数テンプレートの特殊化 -> decltype(x + y) {に対するマングリングの例 return x + y; } struct X { X g++ -std=c++11const &rhs) const $ operator+(X -c main.cpp { nm main.o $ ..... } }; 0000000000000000 W _Z3addI1XEDTplfp_fp0_ERKT_S4_ $ c++filt _Z3addI1XEDTplfp_fp0_ERKT_S4_ decltype int main()({parm#1}+{parm#2}) add<X>(X const&, X const&) { X x, y; add(x, y); } 34
  35. 35. 関数テンプレートの特殊化 に対するマングリング template<typename T> auto add(T const &x, T const &y) -> decltype(x + y) { return x + y; } 35
  36. 36. 関数テンプレートの特殊化 に対するマングリング template<typename T> auto add(T const &x, T const &y) -> decltype(x + y) { return x + y; } 36
  37. 37. 関数テンプレートの特殊化 に対するマングリング template<typename T> auto add(T const &x, T const &y) -> decltype(x + y) { return x + y; } T = X の特殊化をマングリング decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&) 37
  38. 38. 関数テンプレートの特殊化 に対するマングリング template<typename T> auto add(T const &x, T const &y) -> decltype(x + y) { return x + y; } T = X の特殊化をマングリング decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&) 戻り値の型やパラメタ型に現れる式を そのままエンコード 38
  39. 39. 関数テンプレートの特殊化 に対するマングリング template<typename T> auto add(T const &x, T const &y) -> decltype(x + y) { return x + y; } T = X の特殊化をマングリング なぜ??? decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&) 戻り値の型やパラメタ型に現れる式を そのままエンコード 39
  40. 40. 式をそのままマングリングって なんなの? 馬鹿なの? 死ぬ の? decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&) 40
  41. 41. 式をそのままマングリングって なんなの? 馬鹿なの? 死ぬ の? decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&) 1つ目のパラメタが X const の左辺値だと 特殊化の時点で分かる 41
  42. 42. 式をそのままマングリングって なんなの? 馬鹿なの? 死ぬ の? decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&) 1つ目のパラメタが X const の左辺値だと 特殊化の時点で分かる 2つ目のパラメタが X const の左辺値だと 特殊化の時点で分かる 42
  43. 43. 式をそのままマングリングって なんなの? 馬鹿なの? 死ぬ の? decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&) 1つ目のパラメタが X const の左辺値だと 特殊化の時点で分かる 2つ目のパラメタが X const の左辺値だと 特殊化の時点で分かる ゆえにX constの左辺値とX constの左辺値との operator+の戻り値型も特殊化の時点で分かる 43
  44. 44. 式をそのままマングリングって なんなの? 馬鹿なの? 死ぬ の? decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&) 疑問: 1つ目のパラメタが X const の左辺値だと 特殊化の時点で分かる add<X>(X const&, X const&) を X マングリングすればええやん??? 2つ目のパラメタが X const の左辺値だと 特殊化の時点で分かる 戻り値の型やパラメタ型に現れる ゆえにX constの左辺値とX constの左辺値との 定数式や型の計算を全部やった結果を operator+の戻り値型も特殊化の時点で分かる マングリングすればええやん??? 44
  45. 45. 関数テンプレートの特殊化に対 して,なぜ戻り値型やパラメタ 型に現れる定数式や型計算を行 わずにマングリングするのか? 2013/11/02 Cryolite C++ 例外安全 Day 45
  46. 46. まとめ:マングリングは あるプログラム中に 共存することが許されている 異なるエンティティを 区別できないといけない あるプログラム中に 共存することが許されていない エンティティ同士を 区別できる必要はない 46
  47. 47. まとめ:マングリングは あるプログラム中に 可能性1: 共存することが許されている 関数テンプレートの戻り値型やパラメタに表れる定 数式や型の計算をしてからマングリングすると区別 異なるエンティティを すべきものが区別できなくなる 区別できないといけない あるプログラム中に 共存することが許されていない エンティティ同士を 区別できる必要はない 47
  48. 48. まとめ:マングリングは あるプログラム中に 可能性1: 共存することが許されている 関数テンプレートの戻り値型やパラメタに表れる定 数式や型の計算をしてからマングリングすると区別 異なるエンティティを すべきものが区別できなくなる 区別できないといけない あるプログラム中に 可能性2: 共存することが許されていない GCC が従っているマングリング規則は区別する必要 のないものを区別できる余分な能力を持っている エンティティ同士を 区別できる必要はない 48
  49. 49. まとめ:マングリングは あるプログラム中に 可能性1: 共存することが許されている 関数テンプレートの戻り値型やパラメタに表れる定 数式や型の計算をしてからマングリングすると区別 正解 異なるエンティティを すべきものが区別できなくなる 区別できないといけない あるプログラム中に 可能性2: 共存することが許されていない GCC が従っているマングリング規則は区別する必要 のないものを区別できる余分な能力を持っている エンティティ同士を 区別できる必要はない 49
  50. 50. 関数テンプレートの特殊化 に対するマングリングのま とめ 関数テンプレートの特殊化に対して戻 り値型やパラメタ型に現れる定数式や 型の計算をしてからマングリングする と区別すべきものが区別できなくなる 50
  51. 51. 関数テンプレートの特殊化 に対するマングリングのま とめ 関数テンプレートの特殊化に対して戻 り値型やパラメタ型に現れる定数式や 型の計算をしてからマングリングする と区別すべきものが区別できなくなる 名前,テンプレート引数,戻り値の型, パラメタの型のすべてが同じであるよう な,複数の関数テンプレートの特殊化が1 つのプログラム内で共存できる場合があ る 51
  52. 52. 名前,テンプレート引数,戻り 値の型,パラメタの型のすべて が同じであるような関数テンプ レートの特殊化が複数共存する ような well-defined なプログ ラムとは? 2013/11/02 Cryolite C++ 例外安全 Day 52
  53. 53. // generic_add.hpp 思い出そう:マングリング template<typename T> は auto generic_add(T const &x, T const &y) -> decltype(x + y) { return x + y; } template<typename T> auto generic_add(T const &x, T const &y) -> decltype(add(x, y)) { return add(x, y); } 53
  54. 54. // generic_add.hpp T に operator+ が宣言されている 場合のバージョン 思い出そう:マングリング template<typename T> は auto generic_add(T const &x, T const &y) -> decltype(x + y) { } return x + y;T に非メンバ関数 add が 宣言されている場合のバージョン template<typename T> auto generic_add(T const &x, T const &y) -> decltype(add(x, y)) { return add(x, y); } 54
  55. 55. // generic_add.hpp T に operator+ が宣言されている 場合のバージョン 思い出そう:マングリング template<typename T> は auto generic_add(T const &x, T const &y) -> decltype(x + y) { } return x + y;T に非メンバ関数 add が 宣言されている場合のバージョン template<typename T> auto generic_add(T const &x, T const &y) 異なる関数テンプレート定義からインスタンス -> decltype(add(x, y)) { 化された特殊化は,たとえ名前,テンプレート 引数,戻り値の型,パラメタの型のすべてが同 return add(x, y); じであっても共存できる } 55
  56. 56. // x.hpp Struct X ..... // a.cpp #include <x.hpp> #include <generic_add.hpp> // b.cpp #include <x.hpp> #include <generic_add.hpp> X operator+(X const &x, X const &y) { ..... } X add(X const &x, X const &y) { ..... } void f() { X x; X y; generic_add(x, y); } void g() { X x; X y; generic_add(x, y); } 56
  57. 57. // x.hpp Struct X ..... // a.cpp #include <x.hpp> #include <generic_add.hpp> // b.cpp #include <x.hpp> #include <generic_add.hpp> X add(X const &x, X operator+(X const &x, X const &y) X const &y) { { 異なる関数テンプレート定義をインスタンス化 ..... ..... 特殊化が共存可能 } } void f() { X x; X y; generic_add(x, y); } void g() { X x; X y; generic_add(x, y); } 57
  58. 58. // x.hpp struct X ..... // a.cpp #include <x.hpp> #include <generic_add.hpp> // b.cpp #include <x.hpp> #include <generic_add.hpp> X add(X const &x, X operator+(X const &x, _Z11generic_addI1XET_RKS1_S3_ X const &y) X const &y) { 異なる関数テンプレート定義をインスタンス化 { decltype ({parm#1}+{parm#2}) ..... ..... generic_add<X>(X const&, X const&) 特殊化が共存可能 } } void f() { X x; X y; generic_add(x, y); } void g() { X x; X y; generic_add(x, y); } 58
  59. 59. // x.hpp struct X ..... // a.cpp #include <x.hpp> #include <generic_add.hpp> // b.cpp #include <x.hpp> #include <generic_add.hpp> X add(X const &x, X operator+(X const &x, _Z11generic_addI1XET_RKS1_S3_ X const &y) X const &y) { 異なる関数テンプレート定義をインスタンス化 { decltype ({parm#1}+{parm#2}) ..... ..... generic_add<X>(X const&, X const&) 特殊化が共存可能 } } _Z11generic_addI1XEDTcl3addfp_fp0_EERKT_S4_ void g() void f() { { decltype (add({parm#1}, {parm#2})) X x; generic_add<X>(X const&, X const&) X x; X y; X y; generic_add(x, y); generic_add(x, y); } } 59
  60. 60. // x.hpp struct X ..... // a.cpp #include <x.hpp> #include <generic_add.hpp> // b.cpp #include <x.hpp> #include <generic_add.hpp> X add(X const &x, X operator+(X const &x, _Z11generic_addI1XET_RKS1_S3_ X const &y) X const &y) { 異なる関数テンプレート定義をインスタンス化 { decltype ({parm#1}+{parm#2}) ..... ..... generic_add<X>(X const&, X const&) 特殊化が共存可能 } } _Z11generic_addI1XEDTcl3addfp_fp0_EERKT_S4_ void g() void f() { { decltype (add({parm#1}, {parm#2})) X x; generic_add<X>(X const&, X const&) X x; X y; X y; generic_add(x, y); generic_add(x, y); この2つの翻訳単位をリンクしても問題なし } } 60
  61. 61. 関数テンプレートの特殊化に対 するマングリングのまとめ 関数テンプレートの特殊化 に対して,戻り値の型やパ ラメタ型に現れる定数式や 型の計算をせずにそのまま マングリングしなければな らない 61
  62. 62. 捕捉:実際に GCC が従っている ABI はあらゆる式に対するマング リング規則を定めている http://mentorembedded.github. io/cxx-abi/abi.html#mangling 62
  63. 63. ラムダ式の出現に対する制限をなく すには,ラムダ式に対するマングリ ング規則を定めないといけない 63
  64. 64. ラムダ式の出現に対する制限をなく すには,ラムダ式に対するマングリ ング規則を定めないといけない 64
  65. 65. ラムダ式の出現に対する制限をなく すには,ラムダ式に対するマングリ ング規則を定めないといけないが []() { ..... } 65
  66. 66. ラムダ式の出現に対する制限をなく すには,ラムダ式に対するマングリ ング規則を定めないといけないが []() { ..... } ここに出てくる可能性がある,あらゆ る構文要素に対するマングリング規則 を決めなければならない 66
  67. 67. ラムダ式の出現に対する制限をなく すには,ラムダ式に対するマングリ ング規則を定めないといけないが []() { ..... } ここに出てくる可能性がある,あらゆ る構文要素に対するマングリング規則 を決めなければならない 死 67
  68. 68. ラムダ式の出現に対する制限をなく すには,ラムダ式に対するマングリ ング規則を定めないといけないが 68
  69. 69. ラムダ式の出現に対する制限をなく すには,ラムダ式に対するマングリ ング規則を定めないといけないが シンボル名が,あらゆる関 数定義をエンコードできる 能力を有するようになる 69
  70. 70. ラムダ式の出現に対する制限をなく すには,ラムダ式に対するマングリ ング規則を定めないといけないが シンボル名が,あらゆる関 数定義をエンコードできる 能力を有するようになる 意味不明 70
  71. 71. C++11 のラムダ式は なぜ関数テンプレートの戻り値 型やパラメタ型に現れることが できないのか? (再掲) 2013/11/02 Cryolite C++ 例外安全 Day 71
  72. 72. まとめ: • ラムダ式を関数テンプレートの戻り値型やパ ラメタ型のところに置きたい • でも,そうするとマングリングがしんどくな る.だからダメ – 定数式や型をそのままマングリングしないといけ ない – ラムダ式の場合,それがやばいことになる • なんでマングリングがそんなしんどいことに なるの? • そうしないとまずいような well-defined な プログラムが実際に存在しうるから 72

×