Skip to content

Instantly share code, notes, and snippets.

@cjxgm
Last active February 12, 2019 09:00
Show Gist options
  • Save cjxgm/83b08092411ede89ecb3 to your computer and use it in GitHub Desktop.
Save cjxgm/83b08092411ede89ecb3 to your computer and use it in GitHub Desktop.
a simple variant implementation similar to boost::variant, with nil value support, written in C++14.
#pragma once
#include <type_traits>
#include <utility> // for std::forward
#include "utils.hh"
#include "constraints.hh"
#include "const-helper.macro.hh"
namespace meta
{
template <class NIL, class ...MEMBERS>
struct aligned_union : non_transferable
{
using self = aligned_union;
template <class T>
static constexpr auto nil_requirement =
std::is_nothrow_constructible<T>::value &&
std::is_nothrow_destructible <T>::value;
using nil = std::decay_t<NIL>;
static_assert(nil_requirement<nil>, "NIL does not meet the nil-requirement");
static constexpr auto alignment_value()
{
return utils::alignment_of<nil, std::decay_t<MEMBERS>...>;
}
static constexpr auto size()
{
return utils::max(sizeof(nil), sizeof(std::decay_t<MEMBERS>)...);
}
template <class T, class = utils::disable_if_base_of<self, T>>
aligned_union(T&& x) { construct<T>(std::forward<T>(x)); }
aligned_union() : aligned_union{nil{}} {}
template <class T, class ...TS>
void construct(TS&&... xs)
{
using type = std::decay_t<T>;
try {
new (data) type(std::forward<TS>(xs)...);
}
catch (...) {
new (data) nil;
throw;
}
}
template <class T>
void destruct() { data.~T(); }
CONST_HELPER(
template <class T>
auto& as() CONST noexcept
{
using type = utils::const_if<decltype(this), std::decay_t<T>>;
return *reinterpret_cast<type *>(data);
}
);
private:
alignas(alignment_value()) char data[size()];
};
}
#include "const-helper.undef.hh"
#define CONST_HELPER_IMPL_KP(X...) X // kill parenthesis
#define CONST_HELPER_IMPL(BEFORE, AFTER...) \
CONST_HELPER_IMPL_KP BEFORE AFTER \
CONST_HELPER_IMPL_KP BEFORE const AFTER
#define CONST_HELPER(CODE...) CONST_HELPER_IMPL((CODE)
#define CONST ),
#undef CONST
#undef CONST_HELPER
#undef CONST_HELPER_IMPL
#undef CONST_HELPER_IMPL_KP
#pragma once
namespace meta
{
inline namespace constraints
{
struct non_transferable
{
non_transferable() = default;
non_transferable(non_transferable const&) = delete;
non_transferable(non_transferable &&) = delete;
non_transferable & operator = (non_transferable const&) = delete;
non_transferable & operator = (non_transferable &&) = delete;
};
}
}
/* http://en.cppreference.com/w/cpp/language/union
#include <iostream>
// S has one non-static data member (tag), three enumerator members,
// and three variant members (c, n, d)
struct S
{
enum {CHAR, INT, DOUBLE} tag;
union {
char c;
int n;
double d;
};
};
void print_s(const S& s)
{
switch(s.tag)
{
case S::CHAR: std::cout << s.c << '\n'; break;
case S::INT: std::cout << s.n << '\n'; break;
case S::DOUBLE: std::cout << s.d << '\n'; break;
}
}
int main()
{
S s = {S::CHAR, {'a'}};
print_s(s);
s.tag = S::INT; s.n = 123;
print_s(s);
}
*/
#include <iostream>
#include "variant.hh"
using S = meta::variant<char, int, double>;
void print_s(const S& s)
{
s.visit([](auto& x) { std::cout << x << "\n"; });
}
int main()
{
S s{'a'};
print_s(s);
s = 123;
print_s(s);
}
#pragma once
#include <type_traits>
namespace meta
{
namespace utils
{
//------ left fold
//
// foldl(F, x, xs...) => left fold x:xs by F
//
namespace foldl_impl
{
template <class F, class U>
constexpr auto foldl(F, U y) { return y; }
template <class F, class U, class T, class ...TS>
constexpr auto foldl(F f, U y, T x, TS... xs) { return foldl(f, f(y, x), xs...); }
}
using foldl_impl::foldl;
//------ max
//
// max(x, xs...) => maximum value of x:xs
//
namespace max_impl
{
template <class T>
constexpr auto max(T a, T b) { return (a > b ? a : b); }
}
template <class T, class ...TS>
constexpr auto max(T x, TS ...xs)
{
return foldl(max_impl::max<T>, x, xs...);
}
//------ alignment of
//
// alignment_of<TS...> => maximum alignment of TS
//
template <class ...TS>
constexpr auto alignment_of = max(alignof(TS)...);
//------ index of
//
// index_of<U, TS...> => index of U in TS
//
namespace index_of_impl
{
using index_type = decltype(sizeof 0); // same as std::size_t according to the standard
template <index_type I, class U, class ...TS>
constexpr auto index_of = I;
template <index_type I, class U, class T, class ...TS>
constexpr auto index_of<I, U, T, TS...> = index_of<I+1, U, TS...>;
template <index_type I, class U, class ...TS>
constexpr auto index_of<I, U, U, TS...> = I;
template <index_type I, class ...TS>
constexpr auto check()
{
static_assert(I < sizeof...(TS), "not one of the specified types");
return I;
};
template <class U, class ...TS>
constexpr auto checked = check<index_of<0, U, TS...>, TS...>();
}
template <class U, class ...TS>
constexpr auto index_of = index_of_impl::checked<
std::decay_t<U>,
std::decay_t<TS>...
>;
using index_of_impl::index_type;
//------ const if
//
// const_if<U, T> => T
// const_if<U const*, T> => const T
//
namespace const_if_impl
{
template <class U, class T> struct const_if { using type = T; };
template <class U, class T> struct const_if<U const*, T> { using type = const T; };
}
template <class U, class T>
using const_if = typename const_if_impl::const_if<U, T>::type;
//------ disable if base of
//
// disable_if_base_of<U, T> => nothing if U is same of T, or U is the base of T
// => void otherwise
//
template <class U, class T>
using disable_if_base_of = std::enable_if_t<
!std::is_base_of<
std::decay_t<U>,
std::decay_t<T>
>::value
>;
}
}
#include "variant.hh"
#include <iostream>
using std::cerr;
struct foo
{
foo() { cerr << "ctor\n"; }
~foo() { cerr << "dtor\n"; }
foo(foo const&) { cerr << "copy ctor\n"; }
foo(foo &&) { cerr << "move ctor\n"; }
auto& operator = (foo const&) { cerr << "copy asgn\n"; return *this; }
auto& operator = (foo &&) { cerr << "move asgn\n"; return *this; }
};
int main()
{
using va = meta::variant<int, double, foo>;
va v(3);
foo f;
v = f;
v = std::move(f);
double x = 3.2;
v = x;
cerr << v.strict_get<double>() << "\n";
v = foo{};
va b = v;
va c = std::move(v);
cerr << v.strict_get<int>() << "\n";
}
/* output:
ctor
copy ctor
move asgn
dtor
3.2
ctor
move ctor
dtor
copy ctor
move ctor
terminate called after throwing an instance of 'meta::bad_cast'
*/
#pragma once
#include <utility> // for std::forward, std::move
#include "aligned-union.hh"
#include "utils.hh"
#include "const-helper.macro.hh"
namespace meta
{
struct bad_cast {};
template <class ...MEMBERS>
struct variant
{
using self = variant;
template <class T, class = utils::disable_if_base_of<self, T>>
variant(T&& x) : ti{index_of<T>}, au{std::forward<T>(x)} {}
variant() : variant(nil{}) {}
variant(self const& x) { x.visit_with_nil<copy_constructor>({*this}); }
variant(self && x) { x.visit_with_nil<move_constructor>({*this}); }
~variant() { destruct(); }
template <class T, class ...TS>
void emplace(TS&&... xs)
{
destruct();
construct<T>(std::forward<TS>(xs)...);
}
template <class T>
auto is() const noexcept { return (index_of<T> == ti); }
operator bool () const { return !is<nil>(); }
CONST_HELPER(
template <class F>
decltype(auto) visit(F const& f={}) CONST
{
if (!ti) throw bad_cast{};
return visit<F, MEMBERS...>(f, ti-1);
}
);
CONST_HELPER(
template <class T> auto& strict_get() CONST
{
if (is<T>()) return as<T>();
throw bad_cast{};
}
);
template <class T, class = utils::disable_if_base_of<self, T>>
auto& operator = (T&& x)
{
if (is<T>()) as<T>() = std::forward<T>(x);
else emplace<T>(std::forward<T>(x));
return *this;
}
auto& operator = (self x)
{
destruct();
x.visit<move_constructor>({*this});
return *this;
}
private:
using type_index = utils::index_type;
struct nil {};
type_index ti;
aligned_union<nil, MEMBERS...> au;
template <class T>
static constexpr auto index_of = utils::index_of<T, nil, MEMBERS...>;
CONST_HELPER(
template <class T> auto& as() CONST noexcept { return au.template as<T>(); }
);
CONST_HELPER(
template <class F, class T>
decltype(auto) visit(F const& f, type_index) CONST { return f(as<T>()); }
);
CONST_HELPER(
template <class F, class U, class T, class ...TS>
decltype(auto) visit(F const& f, type_index i) CONST
{
if (i) return visit<F, T, TS...>(f, i-1);
return f(as<U>());
}
);
CONST_HELPER(
template <class F>
decltype(auto) visit_with_nil(F const& f={}) CONST
{
return visit<F, nil, MEMBERS...>(f, ti);
}
);
struct destructor
{
template <class T>
void operator () (T& x) const { x.~T(); }
};
void destruct() { visit_with_nil<destructor>(); }
template <class T, class ...TS>
void construct(TS&&... xs)
{
au.template construct<T>(std::forward<TS>(xs)...);
ti = index_of<T>;
}
struct move_constructor
{
variant& self;
move_constructor(variant& self) : self{self} {}
template <class T>
void operator () (T& x) const { self.construct<T>(std::move(x)); }
};
struct copy_constructor
{
variant& self;
copy_constructor(variant& self) : self{self} {}
template <class T>
void operator () (T const& x) const { self.construct<T>(x); }
};
};
}
#include "const-helper.undef.hh"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment