LoginSignup
3
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-02-20

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 時のリソースリーク, 例外非安全性がありますので実用には絶えませんが, 実現方法検討のサンプルということで.

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