テンプレートパラメータにautoを使える喜びを噛み締めて遊ぶ
次期C++標準C++17では、テンプレートパラメータにauto
キーワードを用いる事ができる。この機能により、型に縛られる事なく定数、リテラルをコンパイル時に扱う事ができるので、Variadic templatesで遊ぶのが好きな人からすると中々良い機能なのではないだろうか。ただ、ここ最近までテンプレートパラメータにauto
を用いて正しくコンパイルを行えるコンパイラはまだこの世には無かったのだが、
gcc7.0.0ではtemplate<auto...>でゴリゴリ再帰したりするとinternal compiler error頻出してたけど8.0.0では通るようになっていた。
— roκi (@530506) 2017年4月27日
とあるようにここ最近で遂にゴリゴリ遊べるコンパイラが登場した(勘違いされるとアレなので一応述べておくと、上記ツイートの示しているGCC7はマイナーバージョンで示されている通りまだHead段階だった時の7である)*1。
GCC 7.1 Released! Terrific new optimizations and diagnostics! Great work by GCC Developers. https://t.co/i417Iwgh4i
— GCC - GNU Toolchain (@gnutools) 2017年5月2日
執筆時現在、筆者が確認したテンプレートパラメータへのautoが適切に解釈されるコンパイラは、GCC 7.1.0とGCC 8.0.0 Head、Clang 4.0.0とClang 5.0.0 Headあたりだろうか。とてもアツい!
筆者は、その機能を試す意味合いも込めて、ついこの間コンパイル時ハフマンエンコーダーを書いた。
roki.hateblo.jp
さてそこで、以前まだauto
がテンプレートパラメータ型として使えなかった時に、
roki.hateblo.jp
というものを書いたのだが、auto
版もある程度用意しようという事で、auto
を使える喜びを噛み締めつつ書いて見た。
今回はindex_sequence
の時とは少し異なって、パックの機能としてメンバに持たせる形にしてみた。そのお陰で、lisp schemeのように関数が幾十にも重なって値を内包するかのような見た目ではなく、extention member functionのように横へ処理内容が流れていくような見た目になる。
exp:
#include<srook/cxx17/mpl/any_pack/any_pack.hpp> #include<srook/algorithm/for_each.hpp> #include<experimental/iterator> #include<iostream> #include<vector> #include<boost/type_index.hpp> #include<boost/range/algorithm/copy.hpp> template<auto v> using pred=std::integral_constant<bool,v%2==0>; template<auto... v> std::ostream& operator<<(std::ostream& os,srook::any_pack<v...>) { return os<<boost::typeindex::type_id<srook::any_pack<v...>>().pretty_name(); } int main() { using type=srook::any_pack<4,3,3,1,8,2,9,19>; std::cout<< type::filter_elements<pred>::reverse_type::unique_type() <<std::endl; // フィルターによって偶数のみにしたものをリバースして重複を取り除く std::cout<<std::boolalpha<< type::sort_type<>::binary_search<2> <<std::endl; // ソート(デフォルトだとless)してバイナリサーチ std::cout<< type::filter_elements<pred>::sort_type<>::equal_value<2,4,8> <<std::endl; // フィルターによって偶数のみにしたものをソートしたパックは2,4,8であるか srook::for_each(type::erase_if_all_elements_type<pred>::sort_type<srook::greater>::range<std::tuple>,[](const auto& x){std::cout<<x<<" ";}); // 偶数を除去したパックをgreaterの並びでソートしその値の並びをstd::tupleとして生成、出力 std::cout<<std::endl; boost::copy(type::partial_tail_type<type::size()/2>::replace_elements_type<42,2>::range<std::vector>,std::experimental::make_ostream_joiner(std::cout,",")); // パックの半分から後ろに絞り、その内の値2を42に置換して値の並びをstd::vectorとして生成、出力 std::cout<<std::endl; }
output:
srook::mpl::v1::any_pack<2, 8, 4> true true 19 9 3 3 1 8,42,9,19
まだまだ何か遊びがいがありそうだ。上記の通り、以前はコンパイル時ハフマンエンコーダーを作ったので、古典的な圧縮方法の1つとして、取り敢えずランレングス圧縮も作ってみよう。といっても、any_pack
にこれだけ機能を詰め込ませているので、作るのはかなり簡単だ。
ランレングス圧縮では該当値がどれだけの連続量を持つかを記録する事で圧縮を行うが、愚直に値を記録するだけだと、連続量のないデータに対してはデータ長が増加してしまう恐れがあるため、2つ以上の連続量のある値のみランレングスを行うようにしている。これで、データが増加してしまう事はなくなる。
exp:
#include<iostream> #include<boost/type_index.hpp> #include<srook/cxx17/mpl/any_pack/any_pack.hpp> #include<srook/cxx17/mpl/any_pack/run_length/run_length.hpp> template<auto... v> std::ostream& operator<<(std::ostream& os,srook::any_pack<v...>) { return os<<boost::typeindex::type_id<srook::any_pack<v...>>().pretty_name(); } int main() { using type=srook::any_pack<1,1,5,5,3,3,4,4,4,2>; std::cout<<type()<<std::endl; std::cout<<srook::run_length<type>()<<std::endl;; }
output:
srook::mpl::v1::any_pack<1, 1, 5, 5, 3, 3, 4, 4, 4, 2> srook::mpl::v1::any_pack<1, 2ul, 5, 2ul, 3, 2ul, 4, 3ul, 2>