Lambda in template_final

3,390 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

×