Last active
August 31, 2017 06:06
-
-
Save Bananattack/ac1b3ad2a4bc383370d3c39e4a3d480e to your computer and use it in GitHub Desktop.
C++14 variant type. Table-based visitor dispatch with O(1) time complexity. Improvement over previous tail-recursive version https://gist.github.com/Bananattack/1e2d3bbbf80f9ab63779 -- Probably not as useful in C++17 which has std::variant, but still nice for slightly older compilers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef WIZ_UTILITY_VARIANT_H | |
#define WIZ_UTILITY_VARIANT_H | |
#include <cstddef> | |
#include <cassert> | |
#include <type_traits> | |
#include <utility> | |
namespace wiz { | |
namespace detail { | |
template <std::size_t... Ns> | |
struct MaxValue; | |
template <> | |
struct MaxValue<> { | |
static constexpr std::size_t value = 0; | |
}; | |
template <std::size_t N, std::size_t... Ns> | |
struct MaxValue<N, Ns...> { | |
static constexpr std::size_t value = MaxValue<Ns...>::value > N | |
? MaxValue<Ns...>::value | |
: N; | |
}; | |
template <typename... Ts> | |
struct CommonType {}; | |
template <typename T> | |
struct CommonType<T> { | |
using type = typename std::decay<T>::type; | |
}; | |
template <typename T, typename U> | |
struct CommonType<T, U> { | |
using type = typename std::decay<decltype(true ? std::declval<T>() : std::declval<U>())>::type; | |
}; | |
template <typename T, typename U, typename... Ts> | |
struct CommonType<T, U, Ts...> { | |
using type = typename CommonType<typename CommonType<T, U>::type, Ts...>::type; | |
}; | |
template <typename... Fs> | |
struct Overload; | |
template <typename F> | |
struct Overload<F> { | |
public: | |
Overload(F&& f) : f(std::forward<F>(f)) {} | |
template <typename... Ts> | |
auto operator()(Ts&&... args) const | |
-> decltype(std::declval<F>()(std::forward<Ts>(args)...)) { | |
return f(std::forward<Ts>(args)...); | |
} | |
private: | |
F f; | |
}; | |
template <typename F, typename... Fs> | |
struct Overload<F, Fs...> : Overload<F>, Overload<Fs...> { | |
using Overload<F>::operator(); | |
using Overload<Fs...>::operator(); | |
Overload(F&& f, Fs&&... fs) : | |
Overload<F>(std::forward<F>(f)), | |
Overload<Fs...>(std::forward<Fs>(fs)...) {} | |
}; | |
template <typename U, typename... Ts> | |
struct TypeIndexOf; | |
template <typename U> | |
struct TypeIndexOf<U> { | |
static constexpr int value = -1; | |
}; | |
template <typename U, typename... Ts> | |
struct TypeIndexOf<U, U, Ts...> { | |
static constexpr int value = 0; | |
}; | |
template <typename U, typename T, typename... Ts> | |
struct TypeIndexOf<U, T, Ts...> { | |
static constexpr int value = TypeIndexOf<U, Ts...>::value >= 0 | |
? TypeIndexOf<U, Ts...>::value + 1 | |
: -1; | |
}; | |
template <typename T> | |
void destroyVariantKind(void* data) { | |
reinterpret_cast<typename std::add_pointer<T>::type>(data)->~T(); | |
} | |
template <typename... Ts> | |
void destroyVariant(int tag, void* data) { | |
using Table = void(*)(void*); | |
static const Table table[] = { | |
destroyVariantKind<Ts>... | |
}; | |
return table[tag](data); | |
} | |
template <typename T> | |
void copyVariantKind(void* dest, void* src) { | |
new (dest) T(*reinterpret_cast<typename std::add_pointer<const T>::type>(src)); | |
} | |
template <typename... Ts> | |
void copyVariant(int tag, void* dest, const void* src) { | |
using Table = void(*)(void*, const void*); | |
static const Table table[] = { | |
copyVariantKind<Ts>... | |
}; | |
return table[tag](dest, src); | |
} | |
template <typename T> | |
void moveVariantKind(void* dest, void* src) { | |
new (dest) T(std::move(*reinterpret_cast<typename std::add_pointer<T>::type>(src))); | |
} | |
template <typename... Ts> | |
void moveVariant(int tag, void* dest, void* src) { | |
using Table = void(*)(void*, void*); | |
static const Table table[] = { | |
moveVariantKind<Ts>... | |
}; | |
return table[tag](dest, src); | |
} | |
template <typename F, typename T> | |
auto visitVariantKind(F&& f, const void* data) | |
-> typename std::result_of<F&&(const T&)>::type { | |
return std::forward<F>(f)(*reinterpret_cast<typename std::add_pointer<const T>::type>(data)); | |
} | |
template <typename F, typename... Ts> | |
auto visitVariant(int tag, F&& f, const void* data) { | |
using Result = typename CommonType< | |
typename std::result_of<F&&(const Ts&)>::type... | |
>::type; | |
using Table = Result(*)(F&&, const void*); | |
static const Table table[] = { | |
visitVariantKind<F, Ts>... | |
}; | |
return table[tag](std::forward<F>(f), data); | |
} | |
template <typename U, typename... Ts> | |
void staticAssertVariantTypeValid() { | |
static_assert(detail::TypeIndexOf<U, Ts...>::value >= 0, "Variant does not support the provided type."); | |
} | |
} | |
template <typename... Ts> | |
class Variant { | |
public: | |
Variant() = delete; | |
template <typename U> | |
Variant(const U& value) | |
: tag(detail::TypeIndexOf<U, Ts...>::value) { | |
detail::staticAssertVariantTypeValid<U, Ts...>(); | |
new(&data) U(value); | |
} | |
template <typename U> | |
Variant(U&& value) | |
: tag(detail::TypeIndexOf<U, Ts...>::value) { | |
detail::staticAssertVariantTypeValid<U, Ts...>(); | |
new(&data) U(std::forward<U>(value)); | |
} | |
Variant(const Variant& other) | |
: tag(other.tag) { | |
detail::copyVariant<Ts...>(tag, &data, &other.data); | |
} | |
Variant(Variant&& other) | |
: tag(other.tag) { | |
detail::moveVariant<Ts...>(tag, &data, &other.data); | |
} | |
~Variant() { | |
detail::destroyVariant<Ts...>(tag, &data); | |
} | |
Variant& operator =(const Variant& other) { | |
if (this != &other) { | |
detail::destroyVariant<Ts...>(tag, &data); | |
tag = other.tag; | |
detail::copyVariant<Ts...>(tag, &data, &other.data); | |
} | |
return *this; | |
} | |
Variant& operator =(Variant&& other) { | |
if (this != &other) { | |
detail::destroyVariant<Ts...>(tag, &data); | |
tag = other.tag; | |
detail::moveVariant<Ts...>(tag, &data, &other.data); | |
} | |
return *this; | |
} | |
template <typename U> | |
bool is() const { | |
detail::staticAssertVariantTypeValid<U, Ts...>(); | |
return detail::TypeIndexOf<U, Ts...>::value == tag; | |
} | |
template <typename U> | |
U& get() { | |
detail::staticAssertVariantTypeValid<U, Ts...>(); | |
assert(is<U>()); | |
return *reinterpret_cast<typename std::add_pointer<U>::type>(&data); | |
} | |
template <typename U> | |
const U& get() const { | |
detail::staticAssertVariantTypeValid<U, Ts...>(); | |
assert(is<U>()); | |
return *reinterpret_cast<typename std::add_pointer<const U>::type>(&data); | |
} | |
template <typename U> | |
typename std::add_pointer<U>::type tryGet() { | |
return is<U>() ? &get<U>() : nullptr; | |
} | |
template <typename U> | |
typename std::add_pointer<const U>::type tryGet() const { | |
return is<U>() ? &get<U>() : nullptr; | |
} | |
template <typename F> | |
auto visit(F&& f) const { | |
return detail::visitVariant<F, Ts...>(tag, std::forward<F>(f), &data); | |
} | |
template <typename F, typename... Fs> | |
auto visit(F&& f, Fs&&... fs) const { | |
using OverloadType = detail::Overload<F, Fs...>; | |
return detail::visitVariant<OverloadType, Ts...>( | |
tag, | |
OverloadType(std::forward<F>(f), std::forward<Fs>(fs)...), | |
&data); | |
} | |
private: | |
using DataType = typename std::aligned_storage< | |
detail::MaxValue<sizeof(Ts)...>::value, | |
detail::MaxValue<alignof(Ts)...>::value>::type; | |
int tag; | |
DataType data; | |
}; | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment