#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>
#include "attr.hpp"
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;
}
// TODO all *_or and *_set methods should have a lazy version too
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()
{
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<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;
}
}
// wanting members is *so* common
template<class M, class B>
Option<M> pmd_get(const M B::*pmd) const
{
if (repr.is_some())
{
return Some((*repr.ptr()).*pmd);
}
else
{
return None;
}
}
template<class M, class B>
void pmd_set(M B::*pmd, M value)
{
if (repr.is_some())
{
((*repr.ptr()).*pmd) = std::move(value);
}
}
template<class M, class B>
Option<M> pmd_pget(const M B::*pmd) const
{
if (repr.is_some())
{
return Some((**repr.ptr()).*pmd);
}
else
{
return None;
}
}
template<class M, class B>
void pmd_pset(M B::*pmd, M value)
{
if (repr.is_some())
{
((**repr.ptr()).*pmd) = std::move(value);
}
}
};
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.is_none()) falsy; \
tmwa::option::option_unwrap(std::move(o)); \
}).maybe_ref_fun()
// immediately preceded by 'if'; not double-eval-safe
#define OPTION_IS_SOME_INLOOP(var, expr) \
((expr).is_some()) \
WITH_VAR_INLOOP(auto&, var, *(expr).ptr_or(nullptr))
#define OPTION_IS_SOME_NOLOOP(var, expr) \
((expr).is_some()) \
WITH_VAR_NOLOOP(auto&, var, *(expr).ptr_or(nullptr))
} // namespace option
//using option::Option;
using option::None;
using option::Some;
} // namespace tmwa