#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: template::value && !std::is_constructible::value>* = nullptr> either(Head&& construct_left_head_arg, Tail&&... construct_left_tail_args) noexcept(noexcept(std::declval>()._construct_left(std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...))) : _side(Side::left) { _construct_left(std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...); } template::value && std::is_constructible::value>* = nullptr> either(Head&& construct_right_head_arg, Tail&&... construct_right_tail_args) noexcept(noexcept(std::declval>()._construct_right(std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...))) : _side(Side::right) { _construct_right(std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...); } //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); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _construct_right(rhs._right); // NOLINT(cppcoreguidelines-pro-type-union-access) } } either(either &&rhs) noexcept(noexcept(std::declval>()._construct_left(std::move(rhs._left))) && noexcept(std::declval>()._construct_right(std::move(rhs._right)))) : _side(rhs._side) { if(_side == Side::left) { _construct_left(std::move(rhs._left)); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _construct_right(std::move(rhs._right)); // NOLINT(cppcoreguidelines-pro-type-union-access) } } ~either() { _destruct(); } //TODO Try allowing copy-assignment when Left/Right types are std::is_convertible // NOLINTNEXTLINE(cert-oop54-cpp) either &operator=(const either &rhs) noexcept(noexcept(std::declval>()._construct_left(rhs._left)) && noexcept(std::declval>()._construct_right(rhs._right))) { if (this == &rhs) { return *this; } _destruct(); _side = rhs._side; if (_side == Side::left) { _construct_left(rhs._left); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _construct_right(rhs._right); // NOLINT(cppcoreguidelines-pro-type-union-access) } return *this; } either &operator=(either &&rhs) noexcept(noexcept(std::declval>()._construct_left(std::move(rhs._left))) && noexcept(std::declval>()._construct_right(std::move(rhs._right)))) { if (this == &rhs) { return *this; } _destruct(); _side = rhs._side; if (_side == Side::left) { _construct_left(std::move(rhs._left)); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _construct_right(std::move(rhs._right)); // NOLINT(cppcoreguidelines-pro-type-union-access) } 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& { if (!is_left()) { throw std::logic_error("Tried to get left side of an either which is right."); } return _left; // NOLINT(cppcoreguidelines-pro-type-union-access) } Left &left() & { return const_cast(const_cast*>(this)->left()); } Left &&left() && { return std::move(left()); } const Right &right() const& { if (!is_right()) { throw std::logic_error("Tried to get right side of an either which is left."); } return _right; // NOLINT(cppcoreguidelines-pro-type-union-access) } 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; // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } boost::optional left_opt() & noexcept { if (_side == Side::left) { return _left; // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } // warning: opposed to the other left_opt variants, this one already moves the content and returns by value. boost::optional left_opt() && noexcept(noexcept(boost::optional(std::move(std::declval>()._left)))) { if (_side == Side::left) { return std::move(_left); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } boost::optional right_opt() const& noexcept { if (_side == Side::right) { return _right; // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } boost::optional right_opt() & noexcept { if (_side == Side::right) { return _right; // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } // warning: opposed to the other left_opt variants, this one already moves the content and returns by value. boost::optional right_opt() && noexcept(noexcept(boost::optional(std::move(std::declval>()._right)))) { if (_side == Side::right) { return std::move(_right); // NOLINT(cppcoreguidelines-pro-type-union-access) } 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)...); // NOLINT(cppcoreguidelines-pro-type-union-access) } template void _construct_right(Args&&... args) noexcept(noexcept(new Right(std::forward(args)...))) { new(&_right)Right(std::forward(args)...); // NOLINT(cppcoreguidelines-pro-type-union-access) } void _destruct() noexcept { if (_side == Side::left) { _left.~Left(); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _right.~Right(); // NOLINT(cppcoreguidelines-pro-type-union-access) } } template friend either make_left(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))) */; template friend either make_right(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))) */; }; template inline 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 inline bool operator!=(const either &lhs, const either &rhs) noexcept(noexcept(operator==(lhs, rhs))) { return !operator==(lhs, rhs); } template inline 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 inline either make_left(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))) */ { either result(either::Side::left); result._construct_left(std::forward(args)...); return result; } template inline either make_right(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))) */ { either result(either::Side::right); result._construct_right(std::forward(args)...); return result; } } #endif