diff options
Diffstat (limited to 'src/compat/option.hpp')
-rw-r--r-- | src/compat/option.hpp | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/src/compat/option.hpp b/src/compat/option.hpp new file mode 100644 index 0000000..25c23eb --- /dev/null +++ b/src/compat/option.hpp @@ -0,0 +1,376 @@ +#pragma once +// option.hpp - a data type that may or may not exist +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include <cassert> + +#include <utility> + + +namespace tmwa +{ +namespace option +{ + enum option_hack_type { option_hack_value }; + + template<class T> + class OptionRepr + { + __attribute__((aligned(alignof(T)))) + char _data[sizeof(T)]; + bool _some; + public: + void set_none() { _some = false; } + // maybe add pre_set_some if it is useful for other specializations + void post_set_some() { _some = true; } + bool is_some() const { return _some; } + T *ptr() { return reinterpret_cast<T *>(&_data); } + const T *ptr() const { return reinterpret_cast<const T *>(&_data); } + }; + template<class T> + class OptionRepr<T&>; + template<class T> + class OptionRepr<T&&>; + + template<class T> + Option<T> None(option_hack_type=option_hack_value) + { + return None; + } + + template<class T> + Option<T> Some(T v) + { + Option<T> rv = None; + rv.do_construct(std::move(v)); + return rv; + } + + template<class T> + class Option + { + static_assert(std::is_pod<OptionRepr<T>>::value, "repr should itself be pod, copies are done manually"); + OptionRepr<T> repr; + + friend Option<T> Some<T>(T); + + void do_init() + { + repr.set_none(); + } + template<class... U> + void do_construct(U&&... u) + { + new(repr.ptr()) T(std::forward<U>(u)...); + repr.post_set_some(); + } + void do_move_construct(Option&& r) + { + if (r.repr.is_some()) + { + do_construct(std::move(*r.repr.ptr())); + r.do_destruct(); + } + } + void do_copy_construct(const Option& r) + { + if (r.repr.is_some()) + { + do_construct(*r.repr.ptr()); + } + } + void do_move_assign(Option&& r) + { + if (repr.is_some()) + { + if (r.repr.is_some()) + { + *repr.ptr() = std::move(*r.repr.ptr()); + } + else + { + do_destruct(); + } + return; + } + else + { + do_move_construct(std::move(r)); + } + } + void do_copy_assign(const Option& r) + { + if (repr.is_some()) + { + if (r.repr.is_some()) + { + *repr.ptr() = *r.repr.ptr(); + } + else + { + do_destruct(); + } + return; + } + else + { + do_copy_construct(r); + } + } + void do_destruct() + { + repr.ptr()->~T(); + repr.set_none(); + } + public: + Option() = delete; + Option(Option(*)(option_hack_type)) + { + do_init(); + } + Option(Option&& r) + { + do_init(); + do_move_construct(std::move(r)); + } + Option(const Option& r) + { + do_init(); + do_copy_construct(r); + } + Option& operator = (Option&& r) + { + do_move_assign(std::move(r)); + return *this; + } + Option& operator = (const Option& r) + { + do_copy_assign(r); + return *this; + } + ~Option() + { + do_destruct(); + } + + T move_or(T def) + { + if (repr.is_some()) + { + def = std::move(*repr.ptr()); + do_destruct(); + } + return def; + } + T copy_or(T def) const + { + if (repr.is_some()) + { + def = *repr.ptr(); + } + return def; + } + T& ref_or(T& def) + { + return repr.is_some() ? *repr.ptr() : def; + } + const T& ref_or(const T& def) const + { + return repr.is_some() ? *repr.ptr() : def; + } + T *ptr_or(T *def) + { + return repr.is_some() ? repr.ptr() : def; + } + const T *ptr_or(const T *def) const + { + return repr.is_some() ? repr.ptr() : def; + } + + template<class F> + auto move_map(F&& f) -> Option<decltype(std::forward<F>(f)(std::move(*repr.ptr())))> + { + if (repr.is_some()) + { + auto rv = Some(std::forward<F>(f)(std::move(*repr.ptr()))); + do_destruct(); + return rv; + } + else + { + return None; + } + } + template<class F> + auto map(F&& f) -> Option<decltype(std::forward<F>(f)(*repr.ptr()))> + { + if (repr.is_some()) + { + return Some(std::forward<F>(f)(*repr.ptr())); + } + else + { + return None; + } + } + template<class F> + auto map(F&& f) const -> Option<decltype(std::forward<F>(f)(*repr.ptr()))> + { + if (repr.is_some()) + { + return Some(std::forward<F>(f)(*repr.ptr())); + } + else + { + return None; + } + } + // shortcut for flatten(o.map()) that avoids explicit Some's inside + template<class B, class F> + auto cmap(B&& b, F&& f) const -> Option<decltype(std::forward<F>(f)(*repr.ptr()))> + { + if (repr.is_some() && std::forward<B>(b)(*repr.ptr())) + { + return Some(std::forward<F>(f)(*repr.ptr())); + } + else + { + return None; + } + } + }; + + template<class T> + struct most_flattened_type + { + using type = T; + + static Option<type> flatten(Option<T> o) + { + return std::move(o); + } + }; + template<class T> + struct most_flattened_type<Option<T>> + { + using type = typename most_flattened_type<T>::type; + + static Option<type> flatten(Option<Option<T>> o) + { + return most_flattened_type<T>::flatten(o.move_or(None)); + } + }; + + template<class T> + Option<typename most_flattened_type<T>::type> flatten(Option<T> o) + { + return most_flattened_type<T>::flatten(std::move(o)); + } + + template<class T> + bool operator == (const Option<T>& l, const Option<T>& r) + { + const T *l2 = l.ptr_or(nullptr); + const T *r2 = r.ptr_or(nullptr); + if (!l2 && !r2) + return true; + if (l2 && r2) + { + return *l2 == *r2; + } + return false; + } + template<class T> + bool operator != (const Option<T>& l, const Option<T>& r) + { + return !(l == r); + } + template<class T> + bool operator < (const Option<T>& l, const Option<T>& r) + { + const T *l2 = l.ptr_or(nullptr); + const T *r2 = r.ptr_or(nullptr); + + if (!l2 && r2) + return true; + if (l2 && r2) + { + return *l2 < *r2; + } + return false; + } + template<class T> + bool operator > (const Option<T>& l, const Option<T>& r) + { + return (r < l); + } + template<class T> + bool operator <= (const Option<T>& l, const Option<T>& r) + { + return !(r < l); + } + template<class T> + bool operator >= (const Option<T>& l, const Option<T>& r) + { + return !(l < r); + } + + // workaround for the fact that most references can't escape + template<class T> + struct RefWrapper + { + T maybe_ref; + + T maybe_ref_fun() { return std::forward<T>(maybe_ref); } + }; + + template<class T> + RefWrapper<T> option_unwrap(RefWrapper<Option<T>> o) + { return RefWrapper<T>{std::move(*reinterpret_cast<OptionRepr<T>&>(o.maybe_ref).ptr())}; } + template<class T> + RefWrapper<T&> option_unwrap(RefWrapper<Option<T>&> o) + { return RefWrapper<T&>{*reinterpret_cast<OptionRepr<T>&>(o.maybe_ref).ptr()}; } + template<class T> + RefWrapper<T&&> option_unwrap(RefWrapper<Option<T>&&> o) + { return RefWrapper<T&&>{std::move(*reinterpret_cast<OptionRepr<T>&>(o.maybe_ref).ptr())}; } + template<class T> + RefWrapper<T> option_unwrap(RefWrapper<const Option<T>> o) + { return RefWrapper<T>{std::move(*reinterpret_cast<const OptionRepr<T>&>(o.maybe_ref).ptr())}; } + template<class T> + RefWrapper<const T&> option_unwrap(RefWrapper<const Option<T>&> o) + { return RefWrapper<const T&>{*reinterpret_cast<const OptionRepr<T>&>(o.maybe_ref).ptr()}; } + template<class T> + RefWrapper<const T&&> option_unwrap(RefWrapper<const Option<T>&&> o) + { return RefWrapper<const T&&>{std::move(*reinterpret_cast<const OptionRepr<T>&>(o.maybe_ref).ptr())}; } + + // if you think you understand this, you're not trying hard enough. +#define TRY_UNWRAP(opt, falsy) \ + ({ \ + tmwa::option::RefWrapper<decltype((opt))> o = {(opt)}; \ + if (!o.maybe_ref.ptr_or(nullptr)) falsy; \ + tmwa::option::option_unwrap(std::move(o)); \ + }).maybe_ref_fun() +} // namespace option + +using option::Option; +using option::None; +using option::Some; +} // namespace tmwa |