LoginSignup
1
1

More than 5 years have passed since last update.

Variadic Template を使って boost::variant もどきの Visitor を作ってみた

Last updated at Posted at 2015-02-22

前回の続きです.
operator==で値比較をしていなかったので, できるようにしました.

本家はこちら: http://www.boost.org/doc/libs/1_57_0/doc/html/variant.html

variant_util.h
//
//  variant_util.h
//  メタ関数群
//  Created by atosh on 2/22/15.
//

#ifndef VARIANT_UTIL_H_
#define VARIANT_UTIL_H_

#include <type_traits>

namespace fenf {
namespace detail {

template <typename T, typename... Rest>
struct max_size_t {
private:
    static const int kRestMax = max_size_t<Rest...>::value;

public:
    static const int value = sizeof(T) > kRestMax ? sizeof(T) : kRestMax;
};

template <typename T>
struct max_size_t<T> {
    static const int value = sizeof(T);
};

template <typename Target, typename T, typename... Rest>
struct contains_t {
    static const bool value = (std::is_same<Target, T>::value) ? true : contains_t<Target, Rest...>::value;
};

template <typename Target, typename T>
struct contains_t<Target, T> {
    static const bool value = (std::is_same<Target, T>::value);
};

template <int I, typename Target, typename T, typename... Rest>
struct index_of_impl_t {
    static const int value = (std::is_same<Target, T>::value) ? I : index_of_impl_t<I + 1, Target, Rest...>::value;
};

template <int I, typename Target, typename T>
struct index_of_impl_t<I, Target, T> {
    static const int value = (std::is_same<Target, T>::value) ? I : -1;
};

template <typename Target, typename... Types>
struct index_of_t {
    static const int value = index_of_impl_t<0, Target, Types...>::value;
};

template <int I, typename T, typename... Rest>
struct at_t {
    using type = typename at_t<I - 1, Rest...>::type;
};

template <typename T, typename... Rest>
struct at_t<0, T, Rest...> {
    using type = T;
};

}  // namespace detail
}  // namespace fenf

#endif  // VARIANT_UTIL_H_
variant_visitor.h
//
//  variant_visitor.h
//  apply_visitor の定義
//  Created by atosh on 2/22/15.
//

#ifndef VARIANT_VISITOR_H_
#define VARIANT_VISITOR_H_

#include <type_traits>
#include <cstring>
#include <sstream>

namespace fenf {
namespace detail {

template <class Visitor, class Variant>
typename std::result_of<Visitor(const Variant&)>::type
apply_visitor(Visitor visitor, const Variant& var)
{
    return visitor(var);
}

template <class Visitor, class Variant>
typename std::result_of<Visitor(Variant&)>::type
apply_visitor(Visitor visitor, Variant& var)
{
    return visitor(var);
}

}  // namespace detail

using detail::apply_visitor;

}  // namespace fenf

#endif  // VARIANT_VISITOR_H_
variant.h
//
//  variant.h
//  variant クラスの定義
//  Created by atosh on 2/21/15.
//

#ifndef VARIANT_H_
#define VARIANT_H_

#include <cstring>
#include <sstream>

#include "variant_util.h"
#include "variant_visitor.h"

namespace fenf {
namespace detail {

template <typename T>
struct get_visitor_t {
    template <class Variant>
    const T& operator()(const Variant& var) const
    {
        static_assert(Variant::template contains_t<T>::value, "");
        return *reinterpret_cast<const T* const>(var.storage());
    }
};

template <typename T>
struct mutable_get_visitor_t {
    template <class Variant>
    T& operator()(Variant& var) const
    {
        static_assert(Variant::template contains_t<T>::value, "");
        return *reinterpret_cast<T*>(var.mutable_storage());
    }
};

template <typename T>
struct assign_visitor_t {
    const T& value_;
    assign_visitor_t(const T& value) : value_(value)
    {
    }
    template <class Variant>
    void operator()(Variant& var) const
    {
        static_assert(Variant::template contains_t<T>::value, "");
        static_assert(sizeof(T) <= Variant::kStorageSize, "");
        var.mutable_which() = Variant::template index_of_t<T>::value;
        std::memset(var.mutable_storage(), 0, Variant::kStorageSize);
        new (var.mutable_storage()) T(value_);
    }
};

template <class Variant>
struct copy_visitor_t {
    const Variant& other_;
    copy_visitor_t(const Variant& other) : other_(other)
    {
    }
    void operator()(Variant& var) const
    {
        std::memcpy(var.mutable_storage(), other_.storage(), Variant::kStorageSize);
        var.mutable_which() = other_.which();
    }
};

template <int I, class Variant>
struct is_equal_visitor_t {
    const Variant& other_;
    is_equal_visitor_t(const Variant& other) : other_(other)
    {
    }
    bool operator()(const Variant& var) const
    {
        // valueの型に一致するVisitorを再帰的に探す
        if (var.which() != I) {
            return apply_visitor(is_equal_visitor_t<I - 1, Variant>(other_), var);
        }
        using T = typename Variant::template at_t<I>::type;
        return var.template get<T>() == other_.template get<T>();
    }
};

template <class Variant>
struct is_equal_visitor_t<-1, Variant> {
    is_equal_visitor_t(const Variant&)
    {
    }
    bool operator()(const Variant&) const
    {
        return false;
    }
};

template <int I, class Variant>
struct to_string_visitor_t {
    std::string operator()(const Variant& value) const
    {
        // valueの型に一致するVisitorを再帰的に探す
        if (value.which() != I) {
            return apply_visitor(to_string_visitor_t<I - 1, Variant>(), value);
        }
        using T = typename Variant::template at_t<I>::type;
        std::stringstream ss;
        ss << apply_visitor(get_visitor_t<T>(), value);
        return ss.str();
    }
};

template <class Variant>
struct to_string_visitor_t<-1, Variant> {
    std::string operator()(const Variant& value) const
    {
        return "";
    }
};

//! Variadic Template を使った variant 実装
template <typename... Types>
class variant {
public:
    static const int kNumTypes = sizeof...(Types);                //!< 型数
    static const int kStorageSize = max_size_t<Types...>::value;  //!< 最大サイズを持つ型のサイズ

    //! 指定した型が含まれているか調べるメタ関数
    template <typename T>
    using contains_t = contains_t<T, Types...>;
    //! 指定した型のインデックスを返すメタ関数
    template <typename T>
    using index_of_t = index_of_t<T, Types...>;
    //! 指定したインデックスの型を返すメタ関数
    template <int I>
    using at_t = at_t<I, Types...>;

    variant() : which_(-1)
    {
        std::memset(storage_, 0, kStorageSize);
    }
    variant(const variant& other)
    {
        copy(other);
    }
    variant(variant&& other)
    {
        copy(other);
    }
    template <typename T>
    variant(const T& value)
    {
        assign(value);
    }
    variant& operator=(const variant& other)
    {
        copy(other);
        return *this;
    }
    variant& operator=(variant&& other)
    {
        copy(other);
        return *this;
    }
    template <typename T>
    variant& operator=(const T& value)
    {
        assign(value);
        return *this;
    }
    bool operator==(const variant& other) const
    {
        if (which() != other.which()) {
            return false;
        }
        return apply_visitor(is_equal_visitor_t<kNumTypes - 1, variant>(other), *this);
    }
    bool operator!=(const variant& other) const
    {
        return !(*this == other);
    }
    template <typename T>
    const T&
    get() const
    {
        return apply_visitor(get_visitor_t<T>(), *this);
    }
    template <typename T>
    T&
    get()
    {
        return apply_visitor(mutable_get_visitor_t<T>(), *this);
    }
    std::string
    to_string() const
    {
        return apply_visitor(to_string_visitor_t<kNumTypes - 1, variant>(), *this);
    }

    int
    which() const
    {
        return which_;
    }
    int&
    mutable_which()
    {
        return which_;
    }
    const char* const
    storage() const
    {
        return storage_;
    }
    char*
    mutable_storage()
    {
        return storage_;
    }

private:
    template <typename T>
    void
    assign(const T& value)
    {
        apply_visitor(assign_visitor_t<T>(value), *this);
    }
    void
    assign(const char* const value)
    {
        assign<std::string>(value);
    }
    void
    copy(const variant& other)
    {
        apply_visitor(copy_visitor_t<variant>(other), *this);
    }

    int which_;                   //!< 現在セットされている値の型
    char storage_[kStorageSize];  //!< 値をセットする領域
};

}  // namespace detail

using detail::variant;

}  // namespace fenf

#endif  // VARIANT_H_
main.cpp
//
//  main.cpp
//
//  Created by atosh on 2/21/15.
//

#include <iostream>
#include <string>
#include <cstdint>
#include <cassert>

#include "variant.h"

int
main()
{
    typedef fenf::variant<int, double, std::string> var;
    var a = 10;
    var b = 20;
    var c = 10;
    var d = 3.14;
    var e = "foobar";
    var f = "baz";
    var g = "foobar";
    assert(!(a == b));
    assert(a == c);
    assert(!(a == d));
    assert(!(a == e));
    a.get<int>() += 10;  // mutate
    assert(a == b);
    assert(a.which() == 0);
    a = 3.14;  // mutate
    assert(a.which() == 1);
    assert(a == d);
    assert(e != f);
    assert(e == g);

    assert(a.to_string() == "3.14");
    assert(b.to_string() == "20");
    assert(e.to_string() == "foobar");

    return 0;
}

operator== が呼ばれると is_equal_visitor_t::operator() を呼びます.
variant::which_ に対応する型にキャストする必要があるので, which_ に一致するテンプレート引数 I を持った is_equal_visitor_t を再帰的に線形探索します.
見つかったら I を引数に取り型を返すメタ関数 at_t を使ってキャストし, 値比較します.
同じ要領で to_string も実装してみました.

存在する問題として assign, copy が例外安全でなく, リークもします. boost.variant の解決方法はmelpon氏の記事で解説されています.
また storage_ のアライメントに関する問題も考慮していません. Cryolite氏の記事で解説されています.

実装にあたり Boost.読書会 のノートを参考にさせていただきました.

1
1
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
1
1