Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
variant.hpp 11.35 KiB
/**
 * variant.hpp
 *
 * https://gist.github.com/tibordp/6909880
 *
 * Created on: Feb 28, 2014
 * Author: Tibor Djurica Potpara
 * Modified: Jan Travnicek
 */

#ifndef __VATIANT_HPP_
#define __VATIANT_HPP_

#include <iostream>
#include <utility>
#include <typeinfo>
#include <string>
#include <cstring>
#include <sstream>

#include "compare.hpp"
#include "utility.hpp"
#include "type_traits.hpp"

namespace ext {

template <size_t arg1, size_t ... others>
struct static_max;

template <size_t arg>
struct static_max<arg> {
	static const size_t value = arg;
};

template <size_t arg1, size_t arg2, size_t ... others>
struct static_max<arg1, arg2, others...> {
	static const size_t value = arg1 >= arg2 ? static_max<arg1, others...>::value :
	static_max<arg2, others...>::value;
};

struct type_id_hash_code {
	size_t m_hash_code;

	type_id_hash_code ( size_t hash_code ) : m_hash_code ( hash_code ) {
	}

	friend inline bool operator == ( const type_id_hash_code & first, const type_id_hash_code & second ) {
		return first.m_hash_code == second.m_hash_code;
	}

	friend inline bool operator != ( const type_id_hash_code & first, const type_id_hash_code & second ) {
		return first.m_hash_code != second.m_hash_code;
	}

	friend inline bool operator < ( const type_id_hash_code & first, const type_id_hash_code & second ) {
		return first.m_hash_code < second.m_hash_code;
	}

	friend inline bool operator <= ( const type_id_hash_code & first, const type_id_hash_code & second ) {
		return first.m_hash_code <= second.m_hash_code;
	}

	friend inline bool operator > ( const type_id_hash_code & first, const type_id_hash_code & second ) {
		return first.m_hash_code > second.m_hash_code;
	}

	friend inline bool operator >= ( const type_id_hash_code & first, const type_id_hash_code & second ) {
		return first.m_hash_code >= second.m_hash_code;
	}
};

template<typename... Ts>
struct variant_helper;

template<typename F, typename... Ts>
struct variant_helper<F, Ts...> {
	inline static void destroy(type_id_hash_code id, void * data) {
		if (id == typeid(F).hash_code())
			reinterpret_cast<F*>(data)->~F();
		else
			variant_helper<Ts...>::destroy(id, data);
	}

	inline static void copy(type_id_hash_code old_t, const void * old_v, void * new_v) {
		if (old_t == typeid(F).hash_code())
			new (new_v) F(*reinterpret_cast<const F*>(old_v));
		else
			variant_helper<Ts...>::copy(old_t, old_v, new_v);
	}

	inline static void move(type_id_hash_code old_t, void * old_v, void * new_v) {
		if (old_t == typeid(F).hash_code())
			new (new_v) F(std::move(*reinterpret_cast<F*>(old_v)));
		else
			variant_helper<Ts...>::move(old_t, old_v, new_v);
	}

	inline static void print(std::ostream& out, type_id_hash_code id, const void* data) {
		if (id == typeid(F).hash_code())
			out << *reinterpret_cast<const F*>(data);
		else
			variant_helper<Ts...>::print(out, id, data);
	}

	inline static std::string string(type_id_hash_code id, const void* data) {
		if (id == typeid(F).hash_code())
			return ext::to_string ( *reinterpret_cast<const F*>(data) );
		else
			return variant_helper<Ts...>::string(id, data);
	}

	inline static int compareHelper(type_id_hash_code this_t, const void * this_v, type_id_hash_code other_t, const void * other_v) {
		if (this_t == typeid(F).hash_code() && other_t == typeid(F).hash_code()) {
			static ext::compare<typename std::decay < F >::type > comp;
			return comp( *(reinterpret_cast<const F*>(this_v)), *(reinterpret_cast<const F*>(other_v)));
		} else
			return variant_helper<Ts...>::compareHelper(this_t, this_v, other_t, other_v);
	}
};

template<typename... Ts>
struct variant_helper<void, Ts...> {
	inline static void destroy(type_id_hash_code id, void * data) {
		if (id != typeid(void).hash_code())
			variant_helper<Ts...>::destroy(id, data);
	}

	inline static void copy(type_id_hash_code old_t, const void * old_v, void * new_v) {
		if (old_t != typeid(void).hash_code())
			variant_helper<Ts...>::copy(old_t, old_v, new_v);
	}

	inline static void move(type_id_hash_code old_t, void * old_v, void * new_v) {
		if (old_t != typeid(void).hash_code())
			variant_helper<Ts...>::move(old_t, old_v, new_v);
	}

	inline static void print(std::ostream& out, type_id_hash_code id, const void* data) {
		if (id == typeid(void).hash_code())
			out << "void";
		else
			variant_helper<Ts...>::print(out, id, data);
	}

	inline static std::string string(type_id_hash_code id, const void* data) {
		if (id == typeid(void).hash_code())
			return "void";
		else
			return variant_helper<Ts...>::string(id, data);
	}

	inline static int compareHelper(type_id_hash_code this_t, const void * this_v, type_id_hash_code other_t, const void * other_v) {
		if (this_t == typeid(void).hash_code() && other_t == typeid(void).hash_code())
			return 0;
		else
			return variant_helper<Ts...>::compareHelper(this_t, this_v, other_t, other_v);
	}
};

template<> struct variant_helper<>  {
inline static void destroy(type_id_hash_code, void *) { }
inline static void copy(type_id_hash_code, const void *, void *) { }
inline static void move(type_id_hash_code, void *, void *) { }
inline static void print(std::ostream&, type_id_hash_code, const void *) {}
inline static std::string string(type_id_hash_code, const void *) { return ""; }
inline static int compareHelper(type_id_hash_code, const void *, type_id_hash_code, const void *) { return 0; }
};

template<typename ST, typename AT, typename... Ts>
class variant_base;

template<typename ST, typename AT>
class variant_base<ST, AT> {
	static const size_t data_size = ST::value;
	static const size_t data_align = AT::value;

	using data_t = typename std::aligned_storage<data_size, data_align>::type;

protected:
	type_id_hash_code type_id;
	data_t data;

	variant_base( type_id_hash_code id ) : type_id ( id ) {
		// just to make the -Werror=maybe-uninitialized go away
		std::memset( & data, 0, data_size );
	}
};

template<typename ST, typename AT, typename F, typename... Ts>
class variant_base<ST, AT, F, Ts...> : public variant_base<ST, AT, Ts...> {
	using variant_base<ST, AT, Ts...>::variant_base;

protected:
	variant_base( type_id_hash_code id ) : variant_base<ST, AT, Ts...>::variant_base ( id ) { }

public:
	variant_base(const F& value) : variant_base<ST, AT, Ts...>::variant_base ( type_id_hash_code ( typeid(F).hash_code() ) ) { new (&this->data) F(value); }
	variant_base(F&& value) : variant_base<ST, AT, Ts...>::variant_base ( type_id_hash_code ( typeid(F).hash_code() ) ) { new (&this->data) F(std::move(value)); }

	variant_base() : variant_base<ST, AT, Ts ... >::variant_base ( ) { }
};

template<typename ST, typename AT, typename... Ts>
class variant_base<ST, AT, void, Ts...> : public variant_base<ST, AT, Ts...> {
	using variant_base<ST, AT, Ts...>::variant_base;

protected:
	variant_base( type_id_hash_code id ) : variant_base<ST, AT, Ts...>::variant_base ( id ) { }
public:
	variant_base() : variant_base<ST, AT, Ts...>::variant_base ( type_id_hash_code ( typeid(void).hash_code() ) ) { }
};

template<typename ... Ts>
class variant : public variant_base<static_max<SizeOf<Ts>::size...>, static_max<AlignOf<Ts>::align...>, Ts...> {
	using helper_t = variant_helper<Ts...>;

public:
	using variant_base<static_max<SizeOf<Ts>::size...>, static_max<AlignOf<Ts>::align...>, Ts...>::variant_base;

	template < typename = std::enable_if < std::is_in<void, Ts...>::value > >
	variant ( ) : variant_base<static_max<SizeOf<Ts>::size...>, static_max<AlignOf<Ts>::align...>, Ts...> ( ) {
	}

	//copy consructor
	variant(const variant<Ts...>& old) : variant_base<static_max<SizeOf<Ts>::size...>, static_max<AlignOf<Ts>::align...>, Ts...> ( old.type_id ) {
		helper_t::copy(old.type_id, &old.data, &this->data);
	}

	//move constructor
	variant(variant<Ts...>&& old) noexcept : variant_base<static_max<SizeOf<Ts>::size...>, static_max<AlignOf<Ts>::align...>, Ts...> ( old.type_id ) {
		helper_t::move(old.type_id, &old.data, &this->data);
	}

	// copy assignment operator
	variant<Ts...>& operator =( const variant<Ts...> & other ) {
		return this->operator =( variant <Ts...> ( other ) );
	}

	// move assignment operator
	variant<Ts...>& operator= (variant<Ts...> && old) noexcept {
		using std::swap;

		swap(this->type_id, old.type_id);
		swap(this->data, old.data);

		return *this;
	}

	bool operator== (const variant<Ts...>& other) const {
		return compare ( other ) == 0;
	}

	bool operator!= (const variant<Ts...>& other) const {
		return !(*this == other);
	}

	bool operator< (const variant<Ts...>& other) const {
		return compare ( other ) < 0;
	}

	bool operator> (const variant<Ts...>& other) const {
		return other < *this;
	}

	bool operator<= (const variant<Ts...>& other) const {
		return !(*this > other);
	}

	bool operator>= (const variant<Ts...>& other) const {
		return !(*this < other);
	}

	int compare(const variant<Ts...>& other) const {
		if (this->type_id < other.type_id) return -1;
		if (this->type_id > other.type_id) return 1;

		return helper_t::compareHelper(this->type_id, &this->data, other.type_id, &other.data);
	}

	template<typename T, typename = std::enable_if < std::is_in<T, Ts...>::value > >
	bool is() const {
		return (this->type_id == typeid(T).hash_code());
	}

	template<typename = std::enable_if < std::is_in<void, Ts...>::value > >
	void set ( ) {
		helper_t::destroy(this->type_id, &this->data);
		this->type_id = typeid(void).hash_code();
	}

	template<typename T, typename = std::enable_if < std::is_in<T, Ts...>::value && ! std::is_same < void, T >::value > >
	void set(T&& value) {
		helper_t::destroy(this->type_id, &this->data);
		new (&this->data) T(value);
		this->type_id = typeid(T).hash_code();
	}

	template<typename T, typename = std::enable_if < std::is_in<T, Ts...>::value && ! std::is_same < void, T >::value > >
	void set(const T& value) {
		helper_t::destroy(this->type_id, &this->data);
		new (&this->data) T(std::move(value));
		this->type_id = typeid(T).hash_code();
	}

	template<typename T, typename = std::enable_if < std::is_in<T, Ts...>::value && ! std::is_same < void, T >::value > >
	T& get() {
		// It is a dynamic_cast-like behaviour
		if (this->type_id == typeid(T).hash_code())
			return *reinterpret_cast<T*>(&this->data);
		else
			throw std::bad_cast();
	}

	template<typename T, typename = std::enable_if < std::is_in<T, Ts...>::value && ! std::is_same < void, T >::value > >
	const T& get() const {
		// It is a dynamic_cast-like behaviour
		if (this->type_id == typeid(T).hash_code())
			return *reinterpret_cast<const T*>(&this->data);
		else
			throw std::bad_cast();
	}

	~variant() noexcept {
		helper_t::destroy(this->type_id, &this->data);
	}

	friend std::ostream& operator <<(std::ostream& out, const variant<Ts...>& obj) {
		helper_t::print(out, obj.type_id, &obj.data);
		return out;
	}

	explicit operator std::string() const {
		return helper_t::string(this->type_id, &this->data);
	}

	template < typename T >
	static typename std::enable_if < ! std::is_same < void, T >::value, variant < Ts ... > >::type from ( ) {
		return variant < Ts ... >( T { } );
	}

	template < typename T >
	static typename std::enable_if <   std::is_same < void, T >::value, variant < Ts ... > >::type from ( ) {
		return variant < Ts ... >( );
	}
};

template<typename ... Ts>
struct compare<ext::variant<Ts...>> {
	int operator()(const ext::variant<Ts...>& first, const ext::variant<Ts...>& second) const {
		return first.compare(second);
	}
};

template < class ... Ts  >
std::string to_string ( const ext::variant < Ts ... > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

} /* namespace ext */

#endif /* __VARIANT_HPP_ */