#pragma once // option.hpp - a data type that may or may not exist // // Copyright © 2014 Ben Longbons // // 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 . #include "fwd.hpp" #include #include namespace tmwa { namespace option { enum option_hack_type { option_hack_value }; template 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(&_data); } const T *ptr() const { return reinterpret_cast(&_data); } }; template class OptionRepr; template class OptionRepr; template Option None(option_hack_type=option_hack_value) { return None; } template Option Some(T v) { Option rv = None; rv.do_construct(std::move(v)); return rv; } // TODO all *_or and *_set methods should have a lazy version too template class Option { static_assert(std::is_pod>::value, "repr should itself be pod, copies are done manually"); OptionRepr repr; friend Option Some(T); void do_init() { repr.set_none(); } template void do_construct(U&&... u) { new(repr.ptr()) T(std::forward(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() { if (repr.is_some()) { 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; } bool is_some() const { return repr.is_some(); } bool is_none() const { return !is_some(); } template auto move_map(F&& f) -> Option(f)(std::move(*repr.ptr())))> { if (repr.is_some()) { auto rv = Some(std::forward(f)(std::move(*repr.ptr()))); do_destruct(); return rv; } else { return None; } } template auto map(F&& f) -> Option(f)(*repr.ptr()))> { if (repr.is_some()) { return Some(std::forward(f)(*repr.ptr())); } else { return None; } } template auto map(F&& f) const -> Option(f)(*repr.ptr()))> { if (repr.is_some()) { return Some(std::forward(f)(*repr.ptr())); } else { return None; } } // shortcut for flatten(o.map()) that avoids explicit Some's inside template auto cmap(B&& b, F&& f) const -> Option(f)(*repr.ptr()))> { if (repr.is_some() && std::forward(b)(*repr.ptr())) { return Some(std::forward(f)(*repr.ptr())); } else { return None; } } // wanting members is *so* common template Option pmd_get(const M B::*pmd) const { if (repr.is_some()) { return Some((*repr.ptr()).*pmd); } else { return None; } } template void pmd_set(M B::*pmd, M value) { if (repr.is_some()) { ((*repr.ptr()).*pmd) = std::move(value); } } template Option pmd_pget(const M B::*pmd) const { if (repr.is_some()) { return Some((**repr.ptr()).*pmd); } else { return None; } } template void pmd_pset(M B::*pmd, M value) { if (repr.is_some()) { ((**repr.ptr()).*pmd) = std::move(value); } } }; template struct most_flattened_type { using type = T; static Option flatten(Option o) { return std::move(o); } }; template struct most_flattened_type> { using type = typename most_flattened_type::type; static Option flatten(Option> o) { return most_flattened_type::flatten(o.move_or(None)); } }; template Option::type> flatten(Option o) { return most_flattened_type::flatten(std::move(o)); } template bool operator == (const Option& l, const Option& 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 bool operator != (const Option& l, const Option& r) { return !(l == r); } template bool operator < (const Option& l, const Option& 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 bool operator > (const Option& l, const Option& r) { return (r < l); } template bool operator <= (const Option& l, const Option& r) { return !(r < l); } template bool operator >= (const Option& l, const Option& r) { return !(l < r); } // workaround for the fact that most references can't escape template struct RefWrapper { T maybe_ref; T maybe_ref_fun() { return std::forward(maybe_ref); } }; template RefWrapper option_unwrap(RefWrapper> o) { return RefWrapper{std::move(*reinterpret_cast&>(o.maybe_ref).ptr())}; } template RefWrapper option_unwrap(RefWrapper&> o) { return RefWrapper{*reinterpret_cast&>(o.maybe_ref).ptr()}; } template RefWrapper option_unwrap(RefWrapper&&> o) { return RefWrapper{std::move(*reinterpret_cast&>(o.maybe_ref).ptr())}; } template RefWrapper option_unwrap(RefWrapper> o) { return RefWrapper{std::move(*reinterpret_cast&>(o.maybe_ref).ptr())}; } template RefWrapper option_unwrap(RefWrapper&> o) { return RefWrapper{*reinterpret_cast&>(o.maybe_ref).ptr()}; } template RefWrapper option_unwrap(RefWrapper&&> o) { return RefWrapper{std::move(*reinterpret_cast&>(o.maybe_ref).ptr())}; } // if you think you understand this, you're not trying hard enough. #define TRY_UNWRAP(opt, falsy) \ ({ \ tmwa::option::RefWrapper o = {(opt)}; \ if (o.maybe_ref.is_none()) falsy; \ tmwa::option::option_unwrap(std::move(o)); \ }).maybe_ref_fun() #define OMATCH_BEGIN(expr) \ { \ auto&& _omatch_var = (expr); \ switch (_omatch_var.is_some() ? 1 : 0) \ { \ { \ { \ /*}}}}*/ #define OMATCH_END() \ /*{{{{*/ \ } \ } \ } \ (void) _omatch_var; \ } #define OMATCH_BEGIN_SOME(var, expr) \ OMATCH_BEGIN (expr) \ OMATCH_CASE_SOME (var) #define OMATCH_CASE_SOME(var) \ /*{{{{*/ \ } \ break; \ } \ { \ case 1: \ { \ auto&& var = *_omatch_var.ptr_or(nullptr); \ /*}}}}*/ #define OMATCH_CASE_NONE() \ /*{{{{*/ \ } \ break; \ } \ { \ case 0: \ { \ /*}}}}*/ } // namespace option //using option::Option; using option::None; using option::Some; } // namespace tmwa