タグ付きバリアント型(代数的データ型)

タグ付きタプル型 - yohhoyの日記
タグ付きバリアント型があってもいいじゃない。
Boost.Variant では要素の型で要素にアクセスしますが、代わりにタグ経由でアクセスすることで、

  • 要素の意味が明確になる。
  • 複数の同じ型の要素に別の意味を与えることができる。

という利点が得られます。
同等のことは言語機能の union でも可能ですが、union を直接扱うのは面倒です。
ここでは Boost.Variant をラップしてタグ付きバリアント型としてみました。
使い方を示します。
準備

#include "named_variant.hpp"
namespace nv = named_variants;

基本

// タグ宣言
struct I {};
struct D {};

// タグ付きバリアント
// data Number = I Int | D Double
using Number = nv::named_variant<I, int, D, double>;

void example1()
{
    // 初期化
    Number n = nv::init<I>(42);

    // 格納されている型は nv::type() 関数で取得する
    if (nv::type(n) == typeid(I))
    {
        // 値の取り出しは nv::get<タグ> で行う
        std::cout << "I " << nv::get<I>(n) << std::endl;
    }

    n = nv::init<D>(3.14);

    // パターンマッチ
    nv::match<void>
      ( n
      , nv::case_<I>([](int i) { std::cout << "I " << i << std::endl; })
      , nv::case_<D>([](double d) { std::cout << "D " << d << std::endl; })
      );
}

再帰データ型

struct Leaf {};
struct Branch {};

// 二分木
// data Tree a = Leaf a | Branch (Tree a) (Tree a)
template <class T>
using Tree = typename nv::make_recursive_named_variant
  < Leaf, T
  , Branch, std::pair<nv::recursive_named_variant_, nv::recursive_named_variant_>
  >::type;

// 木の中に要素があるか調べる(深さ優先)
template <class T, class TreeT>
bool elem(T const &x, TreeT const &tree)
{
    return nv::match<bool>
      ( tree
      , nv::case_<Leaf>([&](T const &y)
        {
            return x == y;
        })
      , nv::case_<Branch>([&](std::pair<TreeT, TreeT> const &l_r)
        {
            return elem(x, l_r.first) || elem(x, l_r.second);
        })
      );
}

void example2()
{
    Tree<int> const t = nv::init<Branch>(std::make_pair
      ( nv::init<Branch>(std::make_pair
          ( nv::init<Leaf>(1)
          , nv::init<Leaf>(2)
          ))
      , nv::init<Leaf>(3)
      ));
    if (elem(2, tree))
        std::cout << "'tree' contains 2." << std::endl;
}

Either

struct Left {};
struct Right {};

template <class T, class U>
using Either = nv::named_variant<Left, T, Right, U>;

void example3()
{
    // 同じ型でも OK
    Either<std::string, std::string> const e = nv::init<Right>("Hello, world!");

    nv::match<void>
      ( e
      , nv::case_<Left>([](std::string const &) {})
      , nv::case_<Right>([](std::string const &s) { std::cout << s << std::endl; })
      );
}

実装は https://gist.github.com/4054177 から。
named_variant<Name, Type, ...> は単に variant<fusion::pair<Name, Type>, ...> のエイリアスになっています。