72
1 2013/11/02 Cryolite C++ 例外安全 Day C++11 のラムダ式は なぜ関数テンプレートの戻り値 型やパラメタ型に現れることが できないのか?

Lambda in template_final

Embed Size (px)

Citation preview

Page 1: Lambda in template_final

1

2013/11/02 CryoliteC++ 例外安全 Day

C++11 のラムダ式はなぜ関数テンプレートの戻り値型やパラメタ型に現れることが

できないのか?

Page 2: Lambda in template_final

Q. なぜラムダ式を関数テンプレートの戻り値型やパラメタ型に置きたいのか?

2

そもそも

Page 3: Lambda in template_final

Q. なぜラムダ式を関数テンプレートの戻り値型やパラメタ型に置きたいのか?

A. C++ メタプログラミングにおいて死活問題だから.

3

そもそも

Page 4: Lambda in template_final

例題: std::is_constructibleを実装しなさい

4

Page 5: Lambda in template_final

struct is_constructible<T, Args…>:

がコンパイルできるかどうかを調べる.

5

template<class T>typename add_rvalue_reference<T>::typecreate();

T t(create<Args>()...);

例題: std::is_constructible

を実装しなさい

Page 6: Lambda in template_final

struct is_constructible<T, Args…>:

がコンパイルできるかどうかを調べる.

例題: std::is_constructible

を実装しなさい

6

template<class T>typename add_rvalue_reference<T>::typecreate();

T t(create<Args>()...);

Args... という型を持つ変数で型 T の

コンストラクタを呼べるかどうか,をコンパイル時 bool 値として取り出す.

Page 7: Lambda in template_final

答え (ラムダ式の出現に制限があるとき):

7

実際に GCC (libstdc++) 4.8.2 の実装を見てみましょう

Page 8: Lambda in template_final

答え (ラムダ式の出現に制限がないとき):

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; }

Page 9: Lambda in template_final

ここまでのまとめ:

9

Page 10: Lambda in template_final

ここまでのまとめ:

10

ラムダ式が関数テンプレートの戻り値型やパラメタ型になれるかなれないかは, C++ メタプログラミ

ングにおいて死活問題.

Page 11: Lambda in template_final

ところが C++11 ではラムダ式の出現に制限がある!

11

Page 12: Lambda in template_final

ところが C++11 ではラムダ式の出現に制限がある!

12

(※ラムダ式は constexpr

ではない + ラムダ式はunevaluated operands に出現できない)

Page 13: Lambda in template_final

ところが C++11 ではラムダ式の出現に制限がある!

13

何か理由があるはず!

(※ラムダ式は constexpr

ではない + ラムダ式はunevaluated operands に出現できない)

Page 14: Lambda in template_final

C++11 でラムダ式の出現に制限がある理由:

14

Page 15: Lambda in template_final

C++11 でラムダ式の出現に制限がある理由:

15

マングリングがしんどいから

Page 16: Lambda in template_final

C++11 でラムダ式の出現に制限がある理由:

16

マングリングがしんどいから

……え?

Page 17: Lambda in template_final

17

2013/11/02 CryoliteC++ 例外安全 Day

C++11 における関数テンプレートのマングリング

Page 18: Lambda in template_final

マングリングの例

18

void f(int){.....

}

Page 19: Lambda in template_final

マングリングの例

19

void f(int){.....

}

$ g++ -std=c++ -c main.cpp$ nm main.o0000000000000000 T _Z1fi$ c++filt _Z1fif(int)

Page 20: Lambda in template_final

20

void f(int){.....

}

void f(double){.....

}

マングリングの例

Page 21: Lambda in template_final

21

void f(int){.....

}

void f(double){.....

}

$ g++ -std=c++ -c main.cpp$ nm main.o0000000000000009 T _Z1fd0000000000000000 T _Z1fi$ c++filt _Z1fif(int)$ c++filt _Z1fdf(double)

マングリングの例

Page 22: Lambda in template_final

マングリングは

22

Page 23: Lambda in template_final

マングリングは

23

あるプログラム中に共存することが許されている

異なるエンティティを区別できないといけない

Page 24: Lambda in template_final

24

マングリングの例

// a.cppvoid f(int){.....

}

// b.cppint f(int){.....

}

Page 25: Lambda in template_final

25

マングリングの例

// a.cppvoid f(int){.....

}

$ g++ -std=c++11 -c a.cpp$ nm a.o0000000000000000 T _Z1fi$ c++filt _Z1fif(int)

// b.cppint f(int){.....

}

$ g++ -std=c++11 -c a.cpp$ nm a.o0000000000000000 T _Z1fi$ c++filt _Z1fif(int)

Page 26: Lambda in template_final

26

マングリングの例

// a.cppvoid f(int){.....

}

$ g++ -std=c++11 -c a.cpp$ nm a.o0000000000000000 T _Z1fi$ c++filt _Z1fif(int)

// b.cppint f(int){.....

}

$ g++ -std=c++11 -c a.cpp$ nm a.o0000000000000000 T _Z1fi$ c++filt _Z1fif(int)

$ g++ -std=c++11 -c a.o b.o

(リンク時に定義の異なる関数に対するシンボルが衝突するので何が起きるか分からない)が,元々 well-defined なプログラムじゃないのでそんなことは知ったこっちゃない!

Page 27: Lambda in template_final

マングリングは

27

あるプログラム中に共存することが許されていない

エンティティ同士を区別できる必要はない

Page 28: Lambda in template_final

まとめ:マングリングは

28

あるプログラム中に共存することが許されている

異なるエンティティを区別できないといけない

あるプログラム中に共存することが許されていない

エンティティ同士を区別できる必要はない

Page 29: Lambda in template_final

関数テンプレートの特殊化に対するマングリングの例

29

template<typename T>void f(int){}

int main(){f<int>(0);

}

Page 30: Lambda in template_final

関数テンプレートの特殊化に対するマングリングの例

30

template<typename T>void f(int){}

int main(){f<int>(0);

}

$ g++ -std=c++11 -c main.cpp$ nm main.o0000000000000000 W _Z1fIiEvi$ c++filt _Z1fIiEvivoid f<int>(int)

Page 31: Lambda in template_final

関数テンプレートの特殊化に対するマングリングの例

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);

}

Page 32: Lambda in template_final

関数テンプレートの特殊化に対するマングリングの例

32

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);

}

$ g++ -std=c++11 -c main.cpp$ nm main.o0000000000000000 W _Z3addI1XEDTplfp_fp0_ERKT_S4_

Page 33: Lambda in template_final

関数テンプレートの特殊化に対するマングリングの例

33

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);

}

$ g++ -std=c++11 -c main.cpp$ nm main.o0000000000000000 W _Z3addI1XEDTplfp_fp0_ERKT_S4_$ c++filt _Z3addI1XEDTplfp_fp0_ERKT_S4_

Page 34: Lambda in template_final

関数テンプレートの特殊化に対するマングリングの例

34

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);

}

$ g++ -std=c++11 -c main.cpp$ nm main.o0000000000000000 W _Z3addI1XEDTplfp_fp0_ERKT_S4_$ c++filt _Z3addI1XEDTplfp_fp0_ERKT_S4_decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&)

Page 35: Lambda in template_final

関数テンプレートの特殊化に対するマングリング

35

template<typename T>auto add(T const &x, T const &y) -> decltype(x + y){return x + y;

}

Page 36: Lambda in template_final

関数テンプレートの特殊化に対するマングリング

36

template<typename T>auto add(T const &x, T const &y) -> decltype(x + y){return x + y;

}

Page 37: Lambda in template_final

関数テンプレートの特殊化に対するマングリング

37

template<typename T>auto add(T const &x, T const &y) -> decltype(x + y){return x + y;

}

decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&)

T = X の特殊化をマングリング

Page 38: Lambda in template_final

関数テンプレートの特殊化に対するマングリング

38

template<typename T>auto add(T const &x, T const &y) -> decltype(x + y){return x + y;

}

戻り値の型やパラメタ型に現れる式をそのままエンコード

decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&)

T = X の特殊化をマングリング

Page 39: Lambda in template_final

関数テンプレートの特殊化に対するマングリング

39

template<typename T>auto add(T const &x, T const &y) -> decltype(x + y){return x + y;

}

戻り値の型やパラメタ型に現れる式をそのままエンコード

decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&)

T = X の特殊化をマングリング

なぜ???

Page 40: Lambda in template_final

式をそのままマングリングってなんなの? 馬鹿なの? 死ぬの?

40

decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&)

Page 41: Lambda in template_final

式をそのままマングリングってなんなの? 馬鹿なの? 死ぬの?

41

decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&)

1つ目のパラメタが X const の左辺値だと

特殊化の時点で分かる

Page 42: Lambda in template_final

式をそのままマングリングってなんなの? 馬鹿なの? 死ぬの?

42

decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&)

1つ目のパラメタが X const の左辺値だと

特殊化の時点で分かる

2つ目のパラメタが X const の左辺値だと特殊化の時点で分かる

Page 43: Lambda in template_final

式をそのままマングリングってなんなの? 馬鹿なの? 死ぬの?

43

decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&)

1つ目のパラメタが X const の左辺値だと

特殊化の時点で分かる

2つ目のパラメタが X const の左辺値だと特殊化の時点で分かる

ゆえにX constの左辺値とX constの左辺値とのoperator+の戻り値型も特殊化の時点で分かる

Page 44: Lambda in template_final

式をそのままマングリングってなんなの? 馬鹿なの? 死ぬの?

44

decltype ({parm#1}+{parm#2}) add<X>(X const&, X const&)

1つ目のパラメタが X const の左辺値だと

特殊化の時点で分かる

2つ目のパラメタが X const の左辺値だと特殊化の時点で分かる

ゆえにX constの左辺値とX constの左辺値とのoperator+の戻り値型も特殊化の時点で分かる

疑問:X add<X>(X const&, X const&) を

マングリングすればええやん???

戻り値の型やパラメタ型に現れる定数式や型の計算を全部やった結果をマングリングすればええやん???

Page 45: Lambda in template_final

45

2013/11/02 CryoliteC++ 例外安全 Day

関数テンプレートの特殊化に対して,なぜ戻り値型やパラメタ型に現れる定数式や型計算を行わずにマングリングするのか?

Page 46: Lambda in template_final

まとめ:マングリングは

46

あるプログラム中に共存することが許されている

異なるエンティティを区別できないといけない

あるプログラム中に共存することが許されていない

エンティティ同士を区別できる必要はない

Page 47: Lambda in template_final

まとめ:マングリングは

47

あるプログラム中に共存することが許されている

異なるエンティティを区別できないといけない

あるプログラム中に共存することが許されていない

エンティティ同士を区別できる必要はない

可能性1:関数テンプレートの戻り値型やパラメタに表れる定数式や型の計算をしてからマングリングすると区別

すべきものが区別できなくなる

Page 48: Lambda in template_final

まとめ:マングリングは

48

あるプログラム中に共存することが許されている

異なるエンティティを区別できないといけない

あるプログラム中に共存することが許されていない

エンティティ同士を区別できる必要はない

可能性1:関数テンプレートの戻り値型やパラメタに表れる定数式や型の計算をしてからマングリングすると区別

すべきものが区別できなくなる

可能性2:GCC が従っているマングリング規則は区別する必要のないものを区別できる余分な能力を持っている

Page 49: Lambda in template_final

まとめ:マングリングは

49

あるプログラム中に共存することが許されている

異なるエンティティを区別できないといけない

あるプログラム中に共存することが許されていない

エンティティ同士を区別できる必要はない

可能性1:関数テンプレートの戻り値型やパラメタに表れる定数式や型の計算をしてからマングリングすると区別

すべきものが区別できなくなる

可能性2:GCC が従っているマングリング規則は区別する必要のないものを区別できる余分な能力を持っている

正解

Page 50: Lambda in template_final

関数テンプレートの特殊化に対するマングリングのまとめ

50

関数テンプレートの特殊化に対して戻り値型やパラメタ型に現れる定数式や型の計算をしてからマングリングすると区別すべきものが区別できなくなる

Page 51: Lambda in template_final

関数テンプレートの特殊化に対するマングリングのまとめ

51

関数テンプレートの特殊化に対して戻り値型やパラメタ型に現れる定数式や型の計算をしてからマングリングすると区別すべきものが区別できなくなる

名前,テンプレート引数,戻り値の型,パラメタの型のすべてが同じであるような,複数の関数テンプレートの特殊化が1つのプログラム内で共存できる場合があ

Page 52: Lambda in template_final

52

2013/11/02 CryoliteC++ 例外安全 Day

名前,テンプレート引数,戻り値の型,パラメタの型のすべてが同じであるような関数テンプレートの特殊化が複数共存するような well-defined なプログ

ラムとは?

Page 53: Lambda in template_final

思い出そう:マングリングは

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);

}

Page 54: Lambda in template_final

思い出そう:マングリングは

54

// 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);

}

T に operator+ が宣言されている

場合のバージョン

T に非メンバ関数 add が

宣言されている場合のバージョン

Page 55: Lambda in template_final

思い出そう:マングリングは

55

// 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);

}

T に operator+ が宣言されている

場合のバージョン

T に非メンバ関数 add が

宣言されている場合のバージョン

異なる関数テンプレート定義からインスタンス化された特殊化は,たとえ名前,テンプレート引数,戻り値の型,パラメタの型のすべてが同

じであっても共存できる

Page 56: Lambda in template_final

56

// a.cpp#include <x.hpp>#include <generic_add.hpp>

X operator+(X const &x,X const &y)

{.....

}

void f(){X x;X y;generic_add(x, y);

}

// b.cpp#include <x.hpp>#include <generic_add.hpp>

X add(X const &x,X const &y)

{.....

}

void g(){X x;X y;generic_add(x, y);

}

// x.hppStruct X .....

Page 57: Lambda in template_final

57

// a.cpp#include <x.hpp>#include <generic_add.hpp>

X operator+(X const &x,X const &y)

{.....

}

void f(){X x;X y;generic_add(x, y);

}

// b.cpp#include <x.hpp>#include <generic_add.hpp>

X add(X const &x,X const &y)

{.....

}

void g(){X x;X y;generic_add(x, y);

}

// x.hppStruct X .....

異なる関数テンプレート定義をインスタンス化特殊化が共存可能

Page 58: Lambda in template_final

58

// a.cpp#include <x.hpp>#include <generic_add.hpp>

X operator+(X const &x,X const &y)

{.....

}

void f(){X x;X y;generic_add(x, y);

}

// b.cpp#include <x.hpp>#include <generic_add.hpp>

X add(X const &x,X const &y)

{.....

}

void g(){X x;X y;generic_add(x, y);

}

// x.hppstruct X .....

異なる関数テンプレート定義をインスタンス化特殊化が共存可能

_Z11generic_addI1XET_RKS1_S3_decltype ({parm#1}+{parm#2})generic_add<X>(X const&, X const&)

Page 59: Lambda in template_final

59

// a.cpp#include <x.hpp>#include <generic_add.hpp>

X operator+(X const &x,X const &y)

{.....

}

void f(){X x;X y;generic_add(x, y);

}

// b.cpp#include <x.hpp>#include <generic_add.hpp>

X add(X const &x,X const &y)

{.....

}

void g(){X x;X y;generic_add(x, y);

}

// x.hppstruct X .....

異なる関数テンプレート定義をインスタンス化特殊化が共存可能

_Z11generic_addI1XET_RKS1_S3_decltype ({parm#1}+{parm#2})generic_add<X>(X const&, X const&)

_Z11generic_addI1XEDTcl3addfp_fp0_EERKT_S4_decltype (add({parm#1}, {parm#2}))generic_add<X>(X const&, X const&)

Page 60: Lambda in template_final

60

// a.cpp#include <x.hpp>#include <generic_add.hpp>

X operator+(X const &x,X const &y)

{.....

}

void f(){X x;X y;generic_add(x, y);

}

// b.cpp#include <x.hpp>#include <generic_add.hpp>

X add(X const &x,X const &y)

{.....

}

void g(){X x;X y;generic_add(x, y);

}

// x.hppstruct X .....

異なる関数テンプレート定義をインスタンス化特殊化が共存可能

_Z11generic_addI1XET_RKS1_S3_decltype ({parm#1}+{parm#2})generic_add<X>(X const&, X const&)

_Z11generic_addI1XEDTcl3addfp_fp0_EERKT_S4_decltype (add({parm#1}, {parm#2}))generic_add<X>(X const&, X const&)

この2つの翻訳単位をリンクしても問題なし

Page 61: Lambda in template_final

関数テンプレートの特殊化に対するマングリングのまとめ

61

関数テンプレートの特殊化に対して,戻り値の型やパラメタ型に現れる定数式や型の計算をせずにそのままマングリングしなければならない

Page 62: Lambda in template_final

捕捉:実際に GCC が従っているABI はあらゆる式に対するマングリング規則を定めている

62

http://mentorembedded.github.

io/cxx-abi/abi.html#mangling

Page 63: Lambda in template_final

ラムダ式の出現に対する制限をなくすには,ラムダ式に対するマングリング規則を定めないといけない

63

Page 64: Lambda in template_final

ラムダ式の出現に対する制限をなくすには,ラムダ式に対するマングリング規則を定めないといけない

64

Page 65: Lambda in template_final

ラムダ式の出現に対する制限をなくすには,ラムダ式に対するマングリング規則を定めないといけないが

65

[]() {.....

}

Page 66: Lambda in template_final

ラムダ式の出現に対する制限をなくすには,ラムダ式に対するマングリング規則を定めないといけないが

66

[]() {.....

}

ここに出てくる可能性がある,あらゆる構文要素に対するマングリング規則

を決めなければならない

Page 67: Lambda in template_final

ラムダ式の出現に対する制限をなくすには,ラムダ式に対するマングリング規則を定めないといけないが

67

[]() {.....

}

ここに出てくる可能性がある,あらゆる構文要素に対するマングリング規則

を決めなければならない

Page 68: Lambda in template_final

ラムダ式の出現に対する制限をなくすには,ラムダ式に対するマングリング規則を定めないといけないが

68

Page 69: Lambda in template_final

ラムダ式の出現に対する制限をなくすには,ラムダ式に対するマングリング規則を定めないといけないが

69

シンボル名が,あらゆる関数定義をエンコードできる能力を有するようになる

Page 70: Lambda in template_final

ラムダ式の出現に対する制限をなくすには,ラムダ式に対するマングリング規則を定めないといけないが

70

シンボル名が,あらゆる関数定義をエンコードできる能力を有するようになる

意味不明

Page 71: Lambda in template_final

71

2013/11/02 CryoliteC++ 例外安全 Day

C++11 のラムダ式はなぜ関数テンプレートの戻り値型やパラメタ型に現れることが

できないのか? (再掲)

Page 72: Lambda in template_final

まとめ:

• ラムダ式を関数テンプレートの戻り値型やパラメタ型のところに置きたい

• でも,そうするとマングリングがしんどくなる.だからダメ

– 定数式や型をそのままマングリングしないといけない

– ラムダ式の場合,それがやばいことになる

• なんでマングリングがそんなしんどいことになるの?

• そうしないとまずいような well-defined なプログラムが実際に存在しうるから

72