2015/02/23 追記:
次の記事 を書きました.
boost::variant の実装を覗いてみたらテンプレート引数をプリプロセッサで頑張っているように見えたので, Variadic Template で作ってみたくなりました.
本家はこちら: http://www.boost.org/doc/libs/1_57_0/doc/html/variant.html
variant.hpp
//
// variant.h
//
// Created by atosh on 2/21/15.
//
#ifndef VARIANT_H_
#define VARIANT_H_
#include <type_traits>
#include <cstring>
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);
};
//! 何番目の型か (0 origin)
template <int N, typename Target, typename T, typename... Rest>
struct index_of_impl_t {
static const int value = (std::is_same<Target, T>::value) ? N : index_of_impl_t<N + 1, Target, Rest...>::value;
};
template <int N, typename Target, typename T>
struct index_of_impl_t<N, Target, T> {
static const int value = (std::is_same<Target, T>::value) ? N : -1;
};
//! 何番目の型か (0 origin)
template <typename Target, typename... Types>
struct index_of_t {
static const int value = index_of_impl_t<0, Target, Types...>::value;
};
//! インデックスを指定して型を取得する
template <int N, typename T, typename... Rest>
struct at_t {
using type = typename at_t<N - 1, Rest...>::type;
};
template <typename T, typename... Rest>
struct at_t<0, T, Rest...> {
using type = T;
};
//! variant 実装
template <typename... Types>
class variant {
public:
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;
}
template <typename T>
const T&
get() const
{
return cast<T>();
}
template <typename T>
T&
get()
{
return cast<T>();
}
int
which() const
{
return which_;
}
private:
//! 代入
template <typename T>
void
assign(const T& value)
{
static_assert(contains_t<T, Types...>::value, "");
static_assert(sizeof(value) <= kStorageSize, "");
which_ = index_of_t<T, Types...>::value;
std::memset(storage_, 0, kStorageSize);
new (storage_) T(value);
}
//! 代入, char* の特殊化
void
assign(const char* const value)
{
assign<std::string>(value);
}
//! 型変換
template <typename T>
T&
cast()
{
static_assert(contains_t<T, Types...>::value, "");
return *reinterpret_cast<T*>(storage_);
}
//! 別オブジェクトからのコピー
void
copy(const variant& other)
{
std::memcpy(storage_, other.storage_, kStorageSize);
which_ = other.which_;
}
static const int kStorageSize = max_size_t<Types...>::value;
int which_;
char storage_[kStorageSize];
};
template <typename... Types>
inline bool operator==(const variant<Types...>& left, const variant<Types...>& right)
{
// 比較が浅い
return std::memcmp(&left, &right, sizeof(variant<Types...>)) == 0;
}
template <typename... Types>
inline bool operator!=(const variant<Types...>& left, const variant<Types...>& right)
{
// 比較が浅い
return std::memcmp(&left, &right, sizeof(variant<Types...>)) != 0;
}
} // 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<int64_t, double, std::string> var;
var a = int64_t(10);
var b = int64_t(20);
var c = int64_t(10);
var d = 3.14;
var e = "foobar";
assert(!(a == b));
assert(a == c);
assert(!(a == d));
assert(!(a == e));
a.get<int64_t>() += 10;
assert(a == b);
assert(a.which() == 0);
a = 3.14;
assert(a.which() == 1);
assert(a == d);
return 0;
}
operator== が深い比較をしていないため std::string なんかだと結果が正しくないのが課題です. ちゃんと Visitor クラスを用意せねばなりません.
また assign, copy 時のリソースリーク, 例外非安全性がありますので実用には絶えませんが, 実現方法検討のサンプルということで.