// Originally taken from // https://github.com/cryfs/cryfs/blob/14ad22570ddacef22d5ff139cdff68a54fc8234d/src/cpp-utils/either.h #pragma once #include #include #include namespace c10 { /** * either is a tagged union that holds either an object of type A * or an object of type B. */ template class either final { public: template < class Head, class... Tail, std::enable_if_t< std::is_constructible::value && !std::is_constructible::value>* = nullptr> either(Head&& construct_left_head_arg, Tail&&... construct_left_tail_args) : _side(Side::left) { _construct_left( std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...); } template < class Head, class... Tail, std::enable_if_t< !std::is_constructible::value && std::is_constructible::value>* = nullptr> either(Head&& construct_right_head_arg, Tail&&... construct_right_tail_args) : _side(Side::right) { _construct_right( std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...); } either(const either& rhs) : _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 : _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(); } either& operator=(const either& rhs) { _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) { _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; } bool is_left() const noexcept { return _side == Side::left; } bool is_right() const noexcept { return _side == Side::right; } const Left& left() const& { if (C10_UNLIKELY(!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 (C10_UNLIKELY(!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()); } template Result fold(LeftFoldFunc&& leftFoldFunc, RightFoldFunc&& rightFoldFunc) const { if (Side::left == _side) { return std::forward(leftFoldFunc)(_left); } else { return std::forward(rightFoldFunc)(_right); } } 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) { new (&_left) Left(std::forward( args)...); // NOLINT(cppcoreguidelines-pro-type-union-access) } template void _construct_right(Args&&... 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); template friend either make_right(Args&&... args); }; template inline bool operator==( const either& lhs, const either& rhs) { 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) { 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) { either result(either::Side::left); result._construct_left(std::forward(args)...); return result; } template inline either make_right(Args&&... args) { either result(either::Side::right); result._construct_right(std::forward(args)...); return result; } } // namespace c10