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