Rokiのチラ裏

学生による学習のログ

テンプレートパラメータにautoを使える喜びを噛み締めて遊ぶ

次期C++標準C++17では、テンプレートパラメータにautoキーワードを用いる事ができる。この機能により、型に縛られる事なく定数、リテラルコンパイル時に扱う事ができるので、Variadic templatesで遊ぶのが好きな人からすると中々良い機能なのではないだろうか。ただ、ここ最近までテンプレートパラメータにautoを用いて正しくコンパイルを行えるコンパイラはまだこの世には無かったのだが、

とあるようにここ最近で遂にゴリゴリ遊べるコンパイラが登場した(勘違いされるとアレなので一応述べておくと、上記ツイートの示しているGCC7はマイナーバージョンで示されている通りまだHead段階だった時の7である)*1

執筆時現在、筆者が確認したテンプレートパラメータへの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.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/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>

*1:実を言うと私の手元にあったGCC 7.0.0 Headは3-stage bootstrap buildをdisableにした上でのバイナリなので、色々と断言するには恐れ多いところがある…