ナウなヤングに
バカうけの
イカした
タグ付き共用体
自己紹介
でちまる
http://libdechimal.so
https://twitter.com/decimalbloat
パワポで生産性が3倍,更に最新バージョンの2016だからUXが5倍,すなわ
ち100倍の資料作成力だ.
タグ付き共用体とは
struct u {
int which;
union {
hoge x;
fuga y;
piyo z;
};
};
共用体に今入ってる値の種類を表わすフラグを付けたもの
今回はこれをめっちゃモダナイズした
リポジトリ
https://github.com/dechimal/TaggedUnion
コード例のprefix
using namespace desalt::tagged_union;
template<typename ...Ts>
using u = tagged_union<Ts...>;
基本的な機能
基本
u<int, double> x{_0, 42};
x.which(); // 0
x.get(_0); // 42
x.get(_1); // error
x = {_1, 123.0};
x.which() // 1
x.get(_1) // 123.0;
x.get(_0) // error
// あとコピーとかムーブとか
同じ型どうしでも合併できる
u<int, int> x{_0, 42};
x.get(_0); // 42
x = {_1, 142};
y.get(_1); // 142
ディスパッチ
u<hoge, fuga> x{_0, hoge{}};
auto s = x.dispatch([] (tag<0>) {
return “hoge";
}, [] (tag<1>) {
return “fuga";
});
std::cout << s << std::endl; // hoge
再帰
using tree = u<int, std::tuple<int, _, _>>;
int sum(tree const & t) {
t.when([] (tag<0> n, int n) {
return n;
}, [] (tag<1>, std::tuple<tree, tree> const & t) {
return ::sum(std::get<0>(t)) + ::sum(std::get<1>(t));
});
}
tree t{_1, std::make_tuple(1, tree{_0, 2}, tree{_0, 3})};
std::cout << sum(t) << std::endl; // 6
コピー
u<int> x{_1, 42}, y = x;
y = {_1, 0};
std::cout << x.get(_0) << std::endl; // 42
std::cout << y.get(_1) << std::endl; // 0
deep copyする.
変換
struct hoge {};
struct fuga : hoge {};
u<int, fuga> x{_1, {}};
u<long, hoge> y = x;
要素の数が同じで,対応する位置の要素が全て変換可能なときに変換可能.
色々な型
非負整数 (こう度な政治的判
断に基づく表現)
struct unit {};
using nat = u<unit, _>;
int to_int(nat const & n) {
return n.when([] (tag<0>, unit) {
return 0;
}, [] (tag<1>, nat const & m) {
return ::to_int(m) + 1;
});
}
nat two{_1, nat{_1, nat{_1}}};
std::cout << ::to_int(two) << std::endl; // 2
リスト
template<typename T>
using list = u<unit, std::tuple<T, _>>;
template<typename T, typename Z, typename F>
auto foldr(list<T> const & l, Z z, F f) {
return n.when([] (tag<0>, unit) {
return z;
}, [] (tag<1>, std::tuple<T, list<T>> const & m) {
return f(std::get<0>(m), ::foldr(std::get<1>(m), z, f));
});
}
JSON
using json_value = u<
nullptr,
bool,
double,
std::string,
std::vector<_>,
std::unordered_map<std::string, _>
>;
using json = u<
std::vector<json_value>,
std::unordered_map<std::string, json_value>
>;
Bottom
using botom = u<_>;
この型の有効な値は得られない.
しかし,(コピー/ムーブ)(構築/代入)および破棄はできる.
bottom型を戻り値とする関数の呼びだしは,return以外の方法でしか戻って
こないか,決して制御が戻らないことを表現するのに使える(実際に使って便
利とは言っていない).
[[noreturn]]との違いは,例えばu<hoge, bottom>などという型で,部分的に
制御が戻ってこない場合があることを型上で表現できる(実際に表現して便
利とは言っていない).
高度な機能
不動点コンビネータ
さっきnatの値をintの値に変換するために int to_int(nat) という関数を書いた
が,あれはこう書ける.
n.when(::fix(::tie([] (auto, tag<0>, unit) {
return 0;
}, [] (auto f, tag<1>, nat const & m) -> int {
return f(m);
})));
不動点コンビネータ
n.when(
::fix( ::tie(
[] (auto, tag<0>, unit) { return 0; },
[] (auto f, tag<1>, nat const & m) -> int { return f(m); })));
tieはBoostのmake_overloaded_functionみたいなもので,複数の関数を一つ
にまとめた関数オブジェクト.
実際に呼び出されるのは,tieに与えた順に探索して最初に呼び出せるもの
(だったと思う).
fixは不動点コンビネータで,与えられた関数を再帰呼び出しする.これはラム
ダ式で再帰するために使う.上例の各ラムダ式の最初の引数が,::fix(…)と
同じ関数である.
static if
template<typename T>
auto has_hoge() {
return ::static_if([] (auto, std::enable_if<(sizeof(T) > 12)>::type* = nullptr) {
return std::true_type{};
}, [] (auto) {
return std::false_type{};
});
}
Tのサイズが12以上ならtrue_type,そうでなければfalse_typeの値を返す関
数.
分岐はSFINAEでやっているので異なる型を返せる.
static if
::static_if([] (auto dep, typename decltype(dep(T{}))::hoge * = {}) {
…
}, [] (auto) {
…
});
Tがhogeという名前でメンバ型があるかどうかで分岐する.
最初の引数はdependent type/expressionを作るためのもので,static ifの中
で渡される.これをなくして単に
[] (typename T::hoge * = {}) {}
とするとSFINAEしないで::hogeがないというコンパイルエラーを引き起こす.
相互再帰
using u0 = u<hoge, _, _r1>;
using u1 = u<fuga, _, u0>;
u1 x = …;
これは,
◦ decltype(x.get(_0)) == fuga;
◦ decltype(x.get(_1)) == u1;
◦ decltype(x.get(_2).get(_0)) == hoge
◦ decltype(x.get(_2).get(_1)) == u0;
◦ decltype(x.get(_2).get(_2)) == u1;
となる.
代入の例外安全性
コードごらんなさい.
https://github.com/dechimal/TaggedUnion/blob/7bbb376304913eaecf6e8e0
168e215a62c0dede3/tagged_union.hpp#L312

ナウなヤングにバカうけのイカしたタグ付き共用体