9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Boost.Hana の紹介!

Last updated at Posted at 2019-08-20

はじめに

Boost.Hana は、Boost の新しくて速いメタプログラミングライブラリです。また、公式リファレンスでの説明は以下の通りです。

Hanaは、型と値の両方の計算に適したC++メタプログラミング用のヘッダーオンリーライブラリです。 それが提供する機能は、十分に確立されたBoost.MPLおよびBoost.Fusionライブラリによって提供されるもののスーパーセットです。 C ++ 11/14の実装手法とイディオムを活用することで、Hanaは以前のメタプログラミングライブラリと同等以上のコンパイル時間とランタイムパフォーマンスを誇り、プロセスの表現力を大幅に向上させます。 Hanaはアドホックな方法で簡単に拡張でき、Boost.Fusion、Boost.MPL、および標準ライブラリとすぐに使用できる相互運用を提供します。(主に google 翻訳です)

この記事では、この Boost.Hana の一部の機能をC++Now 2017: Louis Dionne "Fun with Boost.Hana" - YouTubeや公式リファレンスから紹介したいと思っています。ですが、Hana にはここで紹介する他にもたくさんの機能があります! また、Hana の分かりやすいチュートリアルは、公式リファレンス(google 翻訳)にあります。

(Boost の導入方法については、Boostライブラリのビルド方法 - boostjpに詳しく書かれています。
また、サンプルプログラムのコンパイルの際は、C++14 の機能を有効にしてください。(参考ページ))

間違い等ありましたら、コメントや編集リクエストをくださるとありがたいです...!

Hana と 他のライブラリの比較

コンパイル時に型の並びを操作する

まず、Boost.MPL での処理は次のようになるようです。

#include <type_traits>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/remove_if.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/equal.hpp>

namespace mpl = boost::mpl;

// 型のシーケンス
using Types = mpl::vector<int, void, char, void, double>;

// void 型を取り除く
using NoVoid = mpl::remove_if<Types, std::is_void<mpl::_1>>::type;
// -> mpl::vector<int, char, double>

static_assert(boost::mpl::equal<NoVoid, mpl::vector<int, char, double>>::value);


// ポインタに変換
using Ptrs = mpl::transform<Types, std::add_pointer<mpl::_1>>::type;
// -> mpl::vector<int*, void*, char*, void*, double*>

static_assert(boost::mpl::equal<Ptrs, mpl::vector<int*, void*, char*, void*, double*>>::value);

int main() {}

型の並びがmpl::vector型で表現され、それと述語のメタ関数を引数にして、型の並びを操作しています。すべてが型で表現されており、メタプログラミング! という印象です。(このスタイルは、Hana のリファレンスのどこかで "Classic MPL-style" と呼んでいる箇所がありました)

続いて、Boost.Hanaで同様の処理をすると、次のようになるようです。

#include <boost/hana/tuple.hpp>
#include <boost/hana/traits.hpp>
#include <boost/hana/remove_if.hpp>
#include <boost/hana/equal.hpp>

namespace hana = boost::hana;

// 型のシーケンス
auto Types = hana::tuple_t<int, void, char, void, double>;

// void 型を取り除く
auto NoVoid = hana::remove_if(Types, [](auto t) {
    return hana::traits::is_void(t);
}); // -> hana::tuple_t<int, char, double>

static_assert(NoVoid == hana::tuple_t<int, char, double>);

// ポインタに変換
auto Ptrs = hana::transform(Types, [](auto t) {
    return hana::traits::add_pointer(t);
}); // -> hana::tuple_t<int*, void*, char*, void*, double*>

static_assert(Ptrs == hana::tuple_t<int*, void*, char*, void*, double*>);

int main() {}

hana::tuple_t<T...>は型のように見えますが、実際はタプルのオブジェクトのようです。 このように、Boost.Hanaでは、すべてがオブジェクトとして表現されているようです。(おそらく、このあたりがクラシックではないのだと思われます...)また、基本的に型の並びはhana::tupleで表現されているらしいです。そして、ラムダ式を使用できるのがとても便利らしいです。(キャプチャなどで副作用をもたらしたりできるらしいです...)

要素の型によって並びを操作する(実行時)

初めに、Boost.Fusion での処理は次のようになるようです。

#include <string>
#include <type_traits>
#include <cassert>
#include <boost/fusion/algorithm/transformation/remove_if.hpp>
#include <boost/fusion/include/make_vector.hpp>
#include <boost/fusion/include/equal_to.hpp>
#include <boost/mpl/placeholders.hpp>

using namespace std::string_literals;
namespace fusion = boost::fusion;
namespace mpl = boost::mpl;

// 実行時です
int main()
{
    // 様々な型の要素
    auto vector = fusion::make_vector(1, 2.3f, "Hello"s, 3.4, 'x');
    
    // float 型の要素を取り除く
    auto no_floats = fusion::remove_if<
            std::is_floating_point<mpl::_>>(vector);

    assert(no_floats == fusion::make_vector(1, "Hello"s, 'x'));
}

これを、Boost.Hana を使って書くと、次のようになるようです。

#include <string>
#include <cassert>
#include <boost/hana/tuple.hpp>
#include <boost/hana/remove_if.hpp>
#include <boost/hana/traits.hpp>
#include <boost/hana/equal.hpp>

namespace hana = boost::hana;
using namespace std::string_literals;

// 実行時です
int main()
{
    // 様々な型の要素
    auto tuple = hana::make_tuple(1, 2.3f, "Hello"s, 3.4, 'x');

    // float 型の要素を取り除く
    auto no_floats = hana::remove_if(tuple, [](auto const& t) {
       return hana::traits::is_floating_point(hana::typeid_(t));
    });

    assert(no_floats == hana::make_tuple(1, "Hello"s, 'x'));
}

アルゴリズム関数の述語のラムダ式の中で、ラムダ式の引数を、hana::typeid_()関数を適用してからhana::traitsの関数へ渡しているのがポイントでしょうか。この例は、コンパイル時のものではないですが、型で処理をしているという点では、メタプログラミングと言えそうです。(hana::tupleの要素がすべてコンパイル時に構築できる型のものであれば、コンパイル時に処理できます)また、boost/hana/traits.hppでは、標準の<type_traits>と同等の機能が用意されているらしいです。

Hana で 自作の traits を使う

上記のコードに出てきた、hana::is_floating_point()のようなものを自作するためには、以下のようにすれば良さそうです。

#include <string>
#include <vector>
#include <type_traits>
#include <boost/hana/tuple.hpp>
#include <boost/hana/filter.hpp>
#include <boost/hana/traits.hpp>
#include <boost/hana/equal.hpp>

namespace hana = boost::hana;
using namespace std::string_literals;


// 自作メタ関数 イテレータを持つ型かどうかを調べる
template <class T, class = void>
struct has_iterator : std::false_type {};

template <class T>
struct has_iterator<T, std::void_t<typename T::iterator>> : std::true_type {};

// hana で 使える自作の traits
constexpr auto has_iterator_for_hana = hana::trait<has_iterator>;

// 型のシーケンス
auto Types = hana::tuple_t<int, std::string, char, std::vector<int>, double>;

// イテレータを持つ型のみを残す
auto HasItr = hana::filter(Types, [](auto t) {
    // 使用する例
    return has_iterator_for_hana(t);
}); // -> hana::tuple_t<std::string, std::vector<int>>

static_assert(HasItr == hana::tuple_t<std::string, std::vector<int>>);

int main() {}

型の性質を調べて、std::falseまたはstd::trueを継承するようなメタ関数は、hana::trait<T>を使うことで、Hana で使用できるようです。また、型に修飾を施すようなメタ関数は、代わりにboost/hana/type.hppにあるhana::metafunction<T>を使うと良いようです。

constexpr auto remove_cv = hana::metafunction<std::remove_cv>;

hana::filter(), hana::remove_if()などの関数は、Hanaに用意されているその他の関数を組み合わせることで、様々なアルゴリズムが実現可能だと思われます。詳しくは公式リファレンスを参照してください...!

hana::tuple から型の情報を取り出す

hana::tupleから、型を取り出そうと次のようなコードを書いたところ、以下のようになりました。

#include <boost/hana/tuple.hpp>
#include <iostream>
#include <utility>
#include <boost/type_index.hpp>

namespace hana = boost::hana;

// T... に型の情報が入っています
template <typename ... T>
void tuple_to_type(hana::tuple<T...>)
{
    // 型を表示する例
    std::initializer_list<int> swallow =
            { (void( std::cout << boost::typeindex::type_id_with_cvr<T>().pretty_name() << "\n"), 0)... };
}

int main()
{
    // 型のシーケンス
    auto Types = hana::tuple_t<int, double, char, void>;

    // 型の情報を取り出す
    tuple_to_type(Types);
}

出力

boost::hana::type_impl<int>::_
boost::hana::type_impl<double>::_
boost::hana::type_impl<char>::_
boost::hana::type_impl<void>::_

テンプレート関数の引数にtupleを渡すことで、tupleのテンプレート引数の型を取得しようとしています。boost::typeindex::type_id_with_cvr<T>().pretty_name()で、それぞれの型名を取得しています。パラメーターパックT...を展開する際は、Swallowという手法を用いています。(参考リンク C++のパラメータパック基礎&パック展開テクニック - Qiita)実際に実行すると、boost::hana::type_impl<T>::_が得られました。struct hana::type_impl<T>::_の定義を見てみたところ、hana::type_impl<T>::_のテンプレート引数Tは、型エイリアスtypeで取得できるようでした。そこで、次のように書き替えたところ、うまく取得できるようでした。

#include <boost/hana/tuple.hpp>
#include <iostream>
#include <utility>
#include <boost/type_index.hpp>

namespace hana = boost::hana;

// T... に型情報が入っています。 T::type で、型を取得できるようです
template <typename ... T>
void print_tuple_elem_type(hana::tuple<T...>)
{
    // 型を表示する例
    std::initializer_list<int> swallow ={ (void(     // ↓ここを、T から typename T::type に変更しました
        std::cout << boost::typeindex::type_id_with_cvr<typename T::type>().pretty_name() << "\n"
        ), 0)... 
    };
}

int main()
{
    // 型のシーケンス
    auto Types = hana::tuple_t<int, double, char, int, void>;

    // 型の情報を取り出す
    print_tuple_elem_type(Types);
}

出力

int
double
char
int
void

パラメーターパックの型と要素を取得する

パラメーターパックのそれぞれの型と要素を取得するためには、hana::detail::type_at<n, T>::typehana::arg<n>()を用いれば良さそうです。

#include <iostream>
#include <utility>
#include <string>
#include <boost/type_index.hpp>
#include <boost/hana/tuple.hpp>
#include <boost/hana/detail/type_at.hpp>
#include <boost/hana/functional/arg.hpp>

namespace hana = boost::hana;
using namespace std::string_literals;

// パラメーターパック n 番目の要素の型、値を取得します
template <std::size_t n, typename ... T>
void print_param_pac_elem(T ... args)
{
    std::cout
        << "型 : "
        << boost::typeindex::type_id_with_cvr<
               typename hana::detail::type_at<n - 1, T ...>::type>().pretty_name() << "\n"
        << "値 : "
        << hana::arg<n>(args...);
}

int main()
{
    print_param_pac_elem<1>(1, "std::string"s, 'x', 3.14);
                         // ↑1番目              ↑3番目
                         //     ↑2番目               ↑4番目
}

出力

型 : int
値 : 1

hana::detail::type_at<n, T>::type、の n は 0 から始まっていますが、hana::arg<n>()の n は 1 から始まっていることに注意が必要そうです。また、このようにパラメーターパックを展開しなくても、hana::tupleに要素を固めてhanaに用意されている関数を使えば、様々な操作はできそうです。

型の並びから、条件を満たす型の数を調べる

最後に、方の並びから条件を満たす型の数を調べる例を次に示します。

#include <boost/hana/assert.hpp>
#include <boost/hana/equal.hpp>
#include <boost/hana/ext/std/integral_constant.hpp> // なぜか必要のようです
#include <boost/hana/fold_left.hpp>
#include <boost/hana/if.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/plus.hpp>
#include <boost/hana/type.hpp>
#include <boost/hana/tuple.hpp>
#include <type_traits>

namespace hana = boost::hana;

auto types = hana::tuple_t<long, float, short, float, long, long double>;

auto number_of_floats = hana::fold_left(types, hana::int_c<0>, [](auto count, auto t) {

    // もし、引数 t が浮動少数点数なら、引数 count に 1 を足したものを返します
    return hana::if_(hana::trait<std::is_floating_point>(t),
                     count + hana::int_c<1>,
                     count
    );
});

BOOST_HANA_CONSTANT_CHECK(number_of_floats == hana::int_c<3>);
int main() { }

ここで、hana::int_c<n>は、型の世界で整数を表すようなものだと思われます。integral_constant - cpprefjp C++日本語リファレンス と同じ機能を持ち、さらに機能が拡張されたものらしいです。また、hana::fold_leftは、二項演算を使用した左結合の畳み込み(?)のようです。x1,...,xn, 関数 f およびオプションの初期状態を含む構造体がある場合、fold_left は次のように f を適用するとのことです。

f(... f(f(f(x1, x2), x3), x4) ..., xn) // 初期状態なし
f(... f(f(f(f(state, x1), x2), x3), x4) ..., xn) // 初期状態(state)あり

(C++17 から追加された畳み込み式 - cpprefjp C++日本語リファレンスの機能と同等のもののようで、こちらの説明のほうが分かりやすいかも知れません。)

この場合、上の説明の 関数 f は、述語のラムダ式

[](auto count, auto t) {
    return hana::if_(hana::trait<std::is_floating_point>(t),
                     count + hana::int_c<1>,
                     count
    );
}

で、初期状態が hana::int_c<0>、x1,...,xn はそれぞれlong, float, short, float, long, long doubleといった雰囲気です。

実際の動きとしては、まず0, longを引数にとり、longは浮動少数点数ではないので、0を返します。
次に、さきほど返された0floatを引数にとり、floatは浮動少数点数なので、0 + 1を返します。
そして、今返された1shortを引数にとり、shortは浮動少数点数ではないので、1を返す...というように繰り返されて、最終的に浮動少数の数を返す、というようになっているようです。

まとめ

Boost.Hana は型がtupleなどのオブジェクトとして表されるため、ラムダ式を用いたり、実行時のプログラムと同じようなスタイルでメタプログラミングができたりするようです。Boost.MPL のような、クラシック(?)な普通のメタプログラミングのスタイルも良いですが、この Boost.Hana の記述方法も面白そうですね...! また、ここで紹介したようなものは Boost.Hana の機能のごく一部なので、実際に使ってみる際は公式リファレンスを参照してみてください...!

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?