前回の続きです.
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.読書会 のノートを参考にさせていただきました.