Boost.mpl11を読んだ

ldionne/mpl11 · GitHub

自分の大雑把な理解。間違ってたら指摘してもらいたい

mpl11は基本的に遅延評価である。ゆえに極力計算しない

Boxed typesとは多くのメタ関数やtype_wrap などT::typeとできる型のことである

mpl11のメタ関数は基本的にboxed typeを引数に要求する
遅延評価のため、また記法の簡易化のためである

using F=Metafunc<Box>;//このタイミングではまだなにも評価されていない
using a=F::type;//このタイミングでBox::typeを呼び出し、遅延評価される

これはListでも例外ではない。List::typeは自身の型を返す

List<int>::type;//==List<int>

というのは

F<Box>type;
F<List<char,int>>::type;

この二つをList版Box版と作り分けるのはよくない
ゆえにListはboxed typesであり、自身の型を返す

template<class...T>
struct List
{
	using type=List;
};

Lifted metafunctions

T::apply<...>

と出来る型Tのことである
mpl11では直接従来のメタ関数、つまりテンプレートテンプレートパラメータを扱わない
メタ関数もタイプリスト等mpl11の恩恵を受けることができる
関数オブジェクトのようなものである

ちなみにこれもboxed typesであり自身の型を返す



type class

C++的なclassとは関係ない。Haskellの型クラスのことである
C++風にいうとtraitとかコンセプトとかそんな感じ

例えばC++11なタイプリストとC++03時代のModernC++Designに載ってるタイプリストがあるとして、この二つでは当然先頭の要素を返す関数headの実装は異なる

template<class...T>
struct List;//C++11

template<class Head,class Tail>
struct cons;//c++03

これをtraitsを使って実装するのがtype classである

headを実装する型はhead_implのように実装する形名は*_implで統一するらしい




mpl_datatypeについて
実装とかで使う

mpl時代のIteratorについて
つらいのでやめた


monoid ,Functor, Monadoについて
型クラスである。要するにtraitsである。恐れることはない
例えばMaybe(boost::optional的なもの)はFunctorであり、Monadoである
functorはfmapを定義する
fmapはfunctorと関数を取る関数であり
fmapはmaybeであればmaybeがnothing(無効値)であればnothingを
有効値であれば関数を適用して返す
モナドはそれを連鎖できるようにした感じ

私がvisitorパターンが嫌いな理由

私には昔から、GOFデザインパターンというものを学習したときからどうにも『そり』が合わないデザインパターンがあった
visitorパターンである

別に理解できなかったわけではない。使えないわけでもない。実際何度か使う場面もあった
だが、思いだすたび、必要になるたび、私は考えるのだ
「こいつは何かが腐っている!!」

先日、ついに啓示を得た。オラクルだ。
そして私はついに、visitorパターンが嫌いな理由を理解した。理解してしまった

神は私の耳元でこうささやいた
「お前はvisitorパターンの名前が一番嫌いなんだよ」


visitorパターンのギミックはこうだ
1:accepterさんは仮想メンバ関数でvisitorパターンを受け入れる
2:visitorさんは、*thisを受け取る。この時*thisは仮想メンバ関数のものなので派生クラス型になる
3:適切な関数が呼ばれる

という具合だ。ふむ、visitorパターンはいいパターンだ。結婚したい

しかし、私は気づいてしまった。ここで注目されるべきはAcceptさんの努力であり、安全に行われたダウンキャスト的効果である
少なくとも私はそう考えている。注目すべきはVisitorではない

私がそう考えるに至ったコードは以下のようなものだ

apply(a, make_overload([](SubA){}, [](SubB){}));

Visitorクラスは滅びた。しかしそれでもacceptは必要だ。ゆえに本当に必要なのはAccepterの方だ
故にAccepterさんの功績を讃えAccepterパターンと呼んで差し上げるのが少なくともVisitorより適切であると強く感じるのだ

ちなみにこのコードはvariantによって実装されている

C++11なvariantを大雑把に実装した

variant test
variant
C++11仕様だから20個以上の要素が詰め込めるよ!!COOLCATって感じだ
コンストラクタとデストラクタ、あとapplyだけ実装してある
以下にget関数の簡易な実装を示す

template<class T>
struct Get
{
	T operator()(const T&x)const
	{
		return x;
	}
	template<class U>
	T operator()(U&x)const
	{
		throw "F**K!!";
		return *(T*)nullptr;
	}
};
int main()
{
	using v = variant<char, std::string, int>;
	const auto x = v{ std::string("ABC") };
	std::cout << variant_apply<std::string>(Get<std::string>{}, x );
}

variant_applyはどんな数でも引数にとれる
こういうことも可能

//int variant variant double
variant_apply<std::string>(Func{}, 0, x, y ,3.14);

variant型を見つけ出し勝手に内部でwhichしてどうこうする

applyのコンパイル時計算量はO(N0*N1*...*Nn)≒O(N^n)だけどまぁ諦めろ

VS使いだけどwandboxでconstexpr二分木fold関数作った

グィスト
constexpr binary fold

実際に(静的に)実行したものはこちら
wandbox
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

二分木foldとは文字通り二分木で畳み込み関数である
binary_fold(plus{},1,2,3,4,5)は以下のように解決される

((1 + 2) + (3  + (4 + 5)))

foldlやfoldrに比べて再起深度を抑える働きがある
メタプロをしていてこの関数(のTMP版)がよく働くのでなんとなくconstexpr版も作ってみた
ちなみに私のコンパイラはVSなので動かない
動くかもしれないがsproutはVSサポートしてないので試す気もない

tuple操作ライブラリ「tupleple」

ギッハッハブ
Fuyutsubaki/tupleple · GitHub

タプルプルプルタプルプル
ご意見もらえると喜びます

できること一覧

  • at

N番目の要素にアクセスする

  • apply

タプルに関数を適用する

view

おおむね元要素へのアクセスのインデックスを架け替えてるだけである
基本的に遅延評価で、要素にアクセスできて、ムーブできる


  • cat

繋げる

using namespace tupleple;
auto a = std::make_tuple(1, 2);
auto b = std::make_tuple(std::make_unique<int>(42), 'B');
auto c = std::make_tuple(std::string(":-)"), std::string(":-|"));
auto p = view::cat(a, std::move(b), c) | at<2>();//unique_ptr<int>(42)
  • take / drop

先頭N個を拾ったり落としたりする

auto tuple = std::make_tuple(1, std::make_unique<int>(2), 3);
auto x = std::move(tuple) | view::drop<1>();
auto i = at<0>(std::move(x));//unique_ptr<int>(2)
  • filter

条件に合うものだけを残す

auto r = std::make_tuple(false, "ABC", 3.14, 42);
auto c = r | view::filter_if<std::is_integral>();
std::cout << (c | at<1>());
  • map

各要素に関数を適用する

auto r = std::make_tuple(3.5, 1, 3.14, 1.414);
auto c = r | view::map([](double d){return d * 2; });
auto d = c | at<2>();//6.28
  • reverse

順序を逆転させる

auto x = std::make_tuple(1, 2, 3, 4) | view::reverse() | at<0>();
  • zip

複数のタプルをくっつける

auto t1 = std::make_tuple(1, 2, 3);
auto t2 = std::make_tuple('A', 'B', 'C');
auto t3 = std::make_tuple(std::make_unique<int>(42), true, false);

auto result = tupleple::view::zip(t1, t2, std::move(t3));
auto c1 = std::move(result) | at<0>();
auto r = std::move(c1) | at<2>();	//unique_ptr 42

algorithm

  • binary_fold

二分木畳み込み。右でも左でもない

struct plus
{
	template<class L,class R>
	auto operator()(L l,R r)const
	->decltype(l+r)
	{
	return l+r;
	}
};

int main()
{
	using namespace tupleple;
	auto t = std::make_tuple(1, 1.4f, 31.4, 18L);
	auto x = algorithm::binary_fold(t, plus());
}
  • for_each

全要素をなめる。順序が乱れない

type_list

タイプリスト処理。タプルとタイプリストは切っても切り離せない

utility

いろいろ

マクロフリーの健全なテストライブラリをちょっと作ってみた

マクロは邪悪であり害悪である
故にマクロは滅びなければならない
滅ぼさなければならない。存在してはならない。これは人間の義務である

頭の中でワンワンと神が嘶くのでマクロフリーなテストライブラリもどきを作っていた

gist
マクロ無しの健全なテストライブラリ
wandbox
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

使い方
http://melpon.org/wandbox/permlink/wohs9rxnp79ba9UO

struct test1
	:test::TestCase<test1>
{
	void test()
	{
		check(3 / 2 == 1);
		std::cout << "Test1";
	}
};
struct test2
	:test::TestCase<test2>
{
	void test()
	{
		check(true);
		std::cout << "Test2";
	}
};


int main()
{
	for (auto c:test::Test::getList())
	{
		c();
	}
}

そこら辺に書き殴ったTest1とTest2がなんやかんやでTest内のstaticなListに投げられる
その「なんやかんや」の部分を実装してみた

問題点は呼び出すテストにfilterかけたり、テストしない方法がないこと

TestCase<test2,is_tested<test2>::value>

のようにユーザーにメタ関数を作ってもらって不必要な場合は何もしないのが健全であろうか


最初これをライブラリ側でやろうと思ったのだが
is_testedをTestXに対して特殊化する場合はTestXより前に特殊化がされてないといけないわけで、いろいろつらいのであきらめた。つらい

規格的にヤバそうな限定type_hashメタ関数を作ったけどGCCで動かないし、別の実装とかも考えたけどそれもダメくさいしもうだめだ

再帰深度を抑えたtuple的コンテナの構築 - ここは匣
コレの最後の部分を逆に動かしたら指定された方だけに対応するhash関数が作れるのではとか考えてみた

VSとClangでは動いたというのに。2対1だけど規格的にどっちが正しいのかわからない
間違ってる気さえする


コード
http://melpon.org/wandbox/permlink/dI2Ei7FNlqHyHiur
type_hase

ちなみにVSでは違う値が出力される(少ない値のほうが出力される)
つまり値は実装依存

仮にこのコードが動いた場合はsortなどに使える
型はコンパイル時に比較できない(type_infoは非constexpr)ため自由にソートするのが難しかったが、これが動けば何かと便利だろう



どうしても動かない場合たぶんこんな実装になる

struct Base
{
	std::integral_constant<int, 1> f(double){ return{}; }
};
struct Base2
{
	std::integral_constant<int, 2> f(int){return{};}
};

struct D:Base,Base2
{
	using Base::f;	
	using Base2::f;
	std::integral_constant<int, 0> f(double){ return{}; }
};

int main()
{
	D d;
	using R=decltype(d.f(3.14));
	std::cout<<R::value;
	using i=decltype(d.f(1));
	std::cout<<i::value;
}

当初考えてた対数tupleの実装の流用。そしてこれもだめくさい。baseとbase2が同じレベルで同じ型を持ってた場合動かない

一旦typeにuniqueかけて値を割り当て、というのも考えたけどそれよりこっちのほうが効率がいい気がする。unique関数は決して軽くない。むしろunique関数の実装にsortしたいくらいだ