Skip to content

Instantly share code, notes, and snippets.

@calebh
Created April 4, 2021 18:48
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save calebh/fd00632d9c616d4b0c14e7c2865f3085 to your computer and use it in GitHub Desktop.
Save calebh/fd00632d9c616d4b0c14e7c2865f3085 to your computer and use it in GitHub Desktop.
C++ variant indexed by integer with no standard library dependency
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/
#include <iostream>
#include <string>
// Equivalent to std::aligned_storage
template<unsigned int Len, unsigned int Align>
struct aligned_storage {
struct type {
alignas(Align) unsigned char data[Len];
};
};
template <unsigned int arg1, unsigned int ... others>
struct static_max;
template <unsigned int arg>
struct static_max<arg>
{
static const unsigned int value = arg;
};
template <unsigned int arg1, unsigned int arg2, unsigned int ... others>
struct static_max<arg1, arg2, others...>
{
static const unsigned int value = arg1 >= arg2 ? static_max<arg1, others...>::value :
static_max<arg2, others...>::value;
};
template<class T> struct remove_reference { typedef T type; };
template<class T> struct remove_reference<T&> { typedef T type; };
template<class T> struct remove_reference<T&&> { typedef T type; };
template<uint8_t n, typename... Ts>
struct variant_helper_rec;
template<uint8_t n, typename F, typename... Ts>
struct variant_helper_rec<n, F, Ts...> {
inline static void destroy(uint8_t id, void* data)
{
if (n == id) {
reinterpret_cast<F*>(data)->~F();
} else {
variant_helper_rec<n + 1, Ts...>::destroy(id, data);
}
}
inline static void move(uint8_t id, void* from, void* to)
{
if (n == id) {
// This static_cast and use of remove_reference is equivalent to the use of std::move
new (to) F(static_cast<typename remove_reference<F>::type&&>(*reinterpret_cast<F*>(from)));
} else {
variant_helper_rec<n + 1, Ts...>::move(id, from, to);
}
}
inline static void copy(uint8_t id, const void* from, void* to)
{
if (n == id) {
new (to) F(*reinterpret_cast<const F*>(from));
} else {
variant_helper_rec<n + 1, Ts...>::copy(id, from, to);
}
}
};
template<uint8_t n> struct variant_helper_rec<n> {
inline static void destroy(uint8_t id, void* data) { }
inline static void move(uint8_t old_t, void* from, void* to) { }
inline static void copy(uint8_t old_t, const void* from, void* to) { }
};
template<typename... Ts>
struct variant_helper {
inline static void destroy(uint8_t id, void* data) {
variant_helper_rec<0, Ts...>::destroy(id, data);
}
inline static void move(uint8_t id, void* from, void* to) {
variant_helper_rec<0, Ts...>::move(id, from, to);
}
inline static void copy(uint8_t id, const void* old_v, void* new_v) {
variant_helper_rec<0, Ts...>::copy(id, old_v, new_v);
}
};
template<> struct variant_helper<> {
inline static void destroy(uint8_t id, void* data) { }
inline static void move(uint8_t old_t, void* old_v, void* new_v) { }
inline static void copy(uint8_t old_t, const void* old_v, void* new_v) { }
};
template<typename F>
struct variant_helper_static;
template<typename F>
struct variant_helper_static {
inline static void move(void* from, void* to) {
new (to) F(static_cast<typename remove_reference<F>::type&&>(*reinterpret_cast<F*>(from)));
}
inline static void copy(const void* from, void* to) {
new (to) F(*reinterpret_cast<const F*>(from));
}
};
// Given a uint8_t i, selects the ith type from the list of item types
template<uint8_t i, typename... Items>
struct variant_alternative;
template<typename HeadItem, typename... TailItems>
struct variant_alternative<0, HeadItem, TailItems...>
{
using type = HeadItem;
};
template<uint8_t i, typename HeadItem, typename... TailItems>
struct variant_alternative<i, HeadItem, TailItems...>
{
using type = typename variant_alternative<i - 1, TailItems...>::type;
};
template<typename... Ts>
struct variant {
private:
static const unsigned int data_size = static_max<sizeof(Ts)...>::value;
static const unsigned int data_align = static_max<alignof(Ts)...>::value;
using data_t = typename aligned_storage<data_size, data_align>::type;
using helper_t = variant_helper<Ts...>;
template<uint8_t i>
using alternative = typename variant_alternative<i, Ts...>::type;
uint8_t variant_id;
data_t data;
variant(uint8_t id) : variant_id(id) {}
public:
template<uint8_t i>
static variant create(alternative<i>& value)
{
variant ret(i);
variant_helper_static<alternative<i>>::copy(&value, &ret.data);
return ret;
}
template<uint8_t i>
static variant create(alternative<i>&& value) {
variant ret(i);
variant_helper_static<alternative<i>>::move(&value, &ret.data);
return ret;
}
variant(const variant<Ts...>& from) : variant_id(from.variant_id)
{
helper_t::copy(from.variant_id, &from.data, &data);
}
variant(variant<Ts...>&& from) : variant_id(from.variant_id)
{
helper_t::move(from.variant_id, &from.data, &data);
}
variant<Ts...>& operator= (variant<Ts...>& rhs)
{
helper_t::destroy(variant_id, &data);
variant_id = rhs.variant_id;
helper_t::copy(rhs.variant_id, &rhs.data, &data);
return *this;
}
variant<Ts...>& operator= (variant<Ts...>&& rhs)
{
helper_t::destroy(variant_id, &data);
variant_id = rhs.variant_id;
helper_t::move(rhs.variant_id, &rhs.data, &data);
return *this;
}
uint8_t id() {
return variant_id;
}
template<uint8_t i>
void set(alternative<i>& value)
{
helper_t::destroy(variant_id, &data);
variant_id = i;
variant_helper_static<alternative<i>>::copy(&value, &data);
}
template<uint8_t i>
void set(alternative<i>&& value)
{
helper_t::destroy(variant_id, &data);
variant_id = i;
variant_helper_static<alternative<i>>::move(&value, &data);
}
template<uint8_t i>
alternative<i>& get()
{
if (variant_id == i) {
return *reinterpret_cast<alternative<i>*>(&data);
} else {
// Replace std::bad_cast with something else if the standard library is not available
throw std::bad_cast();
}
}
~variant() {
helper_t::destroy(variant_id, &data);
}
};
struct test {
int* holder;
test() {
std::cout << "test()" << std::endl;
holder = new int();
}
test(test&& old) : holder(nullptr) {
std::cout << "test(test&&)" << std::endl;
std::swap(holder, old.holder);
}
test(const test& old) {
std::cout << "test(const test&)" << std::endl;
holder = new int(*old.holder);
}
~test()
{
std::cout << "~test()" << std::endl;
delete holder;
}
};
template<typename T>
struct maybe {
variant<uint8_t, T> data;
maybe(variant<uint8_t, T> initData) : data(initData)
{}
uint8_t id() {
return data.id();
}
uint8_t nothing() {
return data.get<0>();
}
T just() {
return data.get<1>();
}
};
template<typename T>
maybe<T> just(T val) {
return maybe<T>(variant<uint8_t, T>::create<1>(val));
}
template<typename T>
maybe<T> nothing() {
return maybe<T>(variant<uint8_t, T>::create<0>(0));
}
int main() {
using my_var = variant<std::string, test>;
test myTest;
*myTest.holder = 10;
std::cout << "alpha" << std::endl;
my_var d = my_var::create<1>(myTest);
std::cout << "d ID: " << (int) d.id() << std::endl;
std::cout << *d.get<1>().holder << std::endl;
std::cout << "beta" << std::endl;
my_var e = my_var::create<0>("hello world");
std::cout << "e ID: " << (int) e.id() << std::endl;
*d.get<1>().holder = 42;
std::cout << "gamma" << std::endl;
std::cout << *d.get<1>().holder << std::endl;
d = e;
std::cout << "d ID: " << (int) d.id() << std::endl;
std::cout << *myTest.holder << std::endl;
std::cout << "sigma" << std::endl;
std::cout << d.get<0>() << std::endl;
d.set<0>("Gotcha");
std::cout << d.get<0>() << std::endl;
std::cout << "mu" << std::endl;
maybe<std::string> m = just<std::string>("This is a just.");
std::cout << m.just() << std::endl;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment