#pragma once #ifndef MESSMER_CPPUTILS_EITHER_H #define MESSMER_CPPUTILS_EITHER_H #include #include #include "assert/assert.h" namespace cpputils { template class either final { public: //TODO Try allowing construction with any type that std::is_convertible to Left or Right. either(const Left &left) noexcept(noexcept(std::declval>()._construct_left(left))) : _side(Side::left) { _construct_left(left); } either(Left &&left) noexcept(noexcept(std::declval>()._construct_left(std::move(left)))) : _side(Side::left) { _construct_left(std::move(left)); } either(const Right &right) noexcept(noexcept(std::declval>()._construct_right(right))) : _side(Side::right) { _construct_right(right); } either(Right &&right) noexcept(noexcept(std::declval>()._construct_right(std::move(right)))) : _side(Side::right) { _construct_right(std::move(right)); } //TODO Try allowing copy-construction when Left/Right types are std::is_convertible either(const either &rhs) noexcept(noexcept(std::declval>()._construct_left(rhs._left)) && noexcept(std::declval>()._construct_right(rhs._right))) : _side(rhs._side) { if(_side == Side::left) { _construct_left(rhs._left); } else { _construct_right(rhs._right); } } either(either &&rhs) noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) : _side(rhs._side) { if(_side == Side::left) { _construct_left(std::move(rhs._left)); } else { _construct_right(std::move(rhs._right)); } } ~either() { _destruct(); } //TODO Try allowing copy-assignment when Left/Right types are std::is_convertible either &operator=(const either &rhs) noexcept(noexcept(_construct_left(rhs._left)) && noexcept(_construct_right(rhs._right))) { _destruct(); _side = rhs._side; if (_side == Side::left) { _construct_left(rhs._left); } else { _construct_right(rhs._right); } return *this; } either &operator=(either &&rhs) noexcept(noexcept(_construct_left(std::move(rhs._left))) && noexcept(_construct_right(std::move(rhs._right)))) { _destruct(); _side = rhs._side; if (_side == Side::left) { _construct_left(std::move(rhs._left)); } else { _construct_right(std::move(rhs._right)); } return *this; } //TODO fold, map_left, map_right, left_or_else(val), right_or_else(val), left_or_else(func), right_or_else(func) bool is_left() const noexcept { return _side == Side::left; } bool is_right() const noexcept { return _side == Side::right; } const Left &left() const& { ASSERT(is_left(), "Tried to get left side of an either which is right."); return _left; } Left &left() & { return const_cast(const_cast*>(this)->left()); } Left &&left() && { return std::move(left()); } const Right &right() const& { ASSERT(is_right(), "Tried to get right side of an either which is left."); return _right; } Right &right() & { return const_cast(const_cast*>(this)->right()); } Right &&right() && { return std::move(right()); } boost::optional left_opt() const& noexcept { if (_side == Side::left) { return _left; } else { return boost::none; } } boost::optional left_opt() & noexcept { if (_side == Side::left) { return _left; } else { return boost::none; } } boost::optional left_opt() && noexcept { if (_side == Side::left) { return std::move(_left); } else { return boost::none; } } boost::optional right_opt() const& noexcept { if (_side == Side::right) { return _right; } else { return boost::none; } } boost::optional right_opt() & noexcept { if (_side == Side::right) { return _right; } else { return boost::none; } } boost::optional right_opt() && noexcept { if (_side == Side::right) { return std::move(_right); } else { return boost::none; } } private: union { Left _left; Right _right; }; enum class Side : uint8_t {left, right} _side; explicit either(Side side) noexcept : _side(side) {} template void _construct_left(Args&&... args) noexcept(noexcept(new Left(std::forward(args)...))) { new(&_left)Left(std::forward(args)...); } template void _construct_right(Args&&... args) noexcept(noexcept(new Right(std::forward(args)...))) { new(&_right)Right(std::forward(args)...); } void _destruct() noexcept { if (_side == Side::left) { _left.~Left(); } else { _right.~Right(); } } template static constexpr bool left_noexcept_constructible(Args&&... args) { return noexcept(_construct_left(std::forward(args)...)); } template friend either make_left(Args&&... args) noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))); template friend either make_right(Args&&... args) noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))); }; template bool operator==(const either &lhs, const either &rhs) noexcept(noexcept(std::declval() == std::declval()) && noexcept(std::declval() == std::declval())) { if (lhs.is_left() != rhs.is_left()) { return false; } if (lhs.is_left()) { return lhs.left() == rhs.left(); } else { return lhs.right() == rhs.right(); } } template bool operator!=(const either &lhs, const either &rhs) noexcept(noexcept(operator==(lhs, rhs))) { return !operator==(lhs, rhs); } template std::ostream &operator<<(std::ostream &stream, const either &value) { if (value.is_left()) { stream << "Left(" << value.left() << ")"; } else { stream << "Right(" << value.right() << ")"; } return stream; } template either make_left(Args&&... args) noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))) { either result(either::Side::left); result._construct_left(std::forward(args)...); return result; } template either make_right(Args&&... args) noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))) { either result(either::Side::right); result._construct_right(std::forward(args)...); return result; } } #endif