This is a C++17 Either implementation for error handling.
- First intent is I want to improve myself.
- Second intent is I want to create a more expressive solution than variant for exception free error handling.
- Third intent is same type handling without extra variable.
to_left(...) and to_right(...) helper functions for specified side if both types are same in Result.
Left and Right are helper structs for avoid additional bool usage in Result.
result.hpp
#include <type_traits> #include <variant> namespace marklar::result { // Helper template<typename Type> struct Left; template<typename Type> struct Right; template<typename> struct is_left : std::false_type {}; template<typename Type> struct is_left<Left<Type>> : std::true_type {}; template<typename Type> inline constexpr bool is_left_v = is_left<Type>::value; template<typename> struct is_right : std::false_type {}; template<typename Type> struct is_right<Right<Type>> : std::true_type {}; template<typename Type> inline constexpr bool is_right_v = is_right<Type>::value; template<typename Type> inline constexpr Left<Type> to_left(Type const & value) { return Left<Type>{ value }; } template<typename Type, typename std::enable_if_t<std::is_move_constructible_v<Type>, bool> = true> inline constexpr Left<Type> to_left(Type && value) { return Left<Type>{ std::forward<Type>(value) }; } template<typename Type> inline constexpr Right<Type> to_right(Type const & value) { return Right<Type>{ value }; } template<typename Type, typename std::enable_if_t<std::is_move_constructible_v<Type>, bool> = true> inline constexpr Right<Type> to_right(Type && value) { return Right<Type>{ std::forward<Type>(value) }; } template<typename Type> struct Left { Type const value_; template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Left<Type>, ParamType> && std::is_constructible_v<Type, ParamType &&> && std::is_convertible_v<ParamType &&, Type> , bool> = true> constexpr Left(ParamType && value) : value_ { std::forward<ParamType>(value) } {} template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Left<Type>, ParamType> && std::is_constructible_v<Type, ParamType &&> && !std::is_convertible_v<ParamType &&, Type> , bool> = false> constexpr explicit Left(ParamType && value) : value_ { std::forward<ParamType>(value) } {} template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Type, ParamType> && std::is_constructible_v<Type, ParamType const &> && !std::is_convertible_v<ParamType const &, Type> , bool> = false> explicit constexpr Left(Left<ParamType> const & other) : value_ { other.value_ } {} template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Type, ParamType> && std::is_constructible_v<Type, ParamType &&> && std::is_convertible_v<ParamType &&, Type> , bool> = true> constexpr Left(Left<ParamType> && other) : value_ { std::move(other).value_ } {} template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Type, ParamType> && std::is_constructible_v<Type, ParamType &&> && !std::is_convertible_v<ParamType &&, Type> , bool> = false> explicit constexpr Left(Left<ParamType> && other) : value_ { std::move(other).value_ } {} }; template<typename Type> struct Right { Type const value_; template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Right<Type>, ParamType> && std::is_constructible_v<Type, ParamType &&> && std::is_convertible_v<ParamType &&, Type> , bool> = true> constexpr Right(ParamType && value) : value_ { std::forward<ParamType>(value) } {} template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Right<Type>, ParamType> && std::is_constructible_v<Type, ParamType &&> && !std::is_convertible_v<ParamType &&, Type> , bool> = false> constexpr explicit Right(ParamType && value) : value_ { std::forward<ParamType>(value) } {} template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Type, ParamType> && std::is_constructible_v<Type, ParamType const &> && !std::is_convertible_v<ParamType const &, Type> , bool> = false> explicit constexpr Right(Right<ParamType> const & other) : value_ { other.value_ } {} template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Type, ParamType> && std::is_constructible_v<Type, ParamType &&> && std::is_convertible_v<ParamType &&, Type> , bool> = true> constexpr Right(Right<ParamType> && other) : value_ { std::move(other).value_ } {} template <typename ParamType = Type, typename std::enable_if_t< !std::is_same_v<Type, ParamType> && std::is_constructible_v<Type, ParamType &&> && !std::is_convertible_v<ParamType &&, Type> , bool> = false> explicit constexpr Right(Right<ParamType> && other) : value_ { std::move(other).value_ } {} }; template<typename LeftType, typename RightType> struct Result { static_assert(!(std::is_reference_v<LeftType> || std::is_reference_v<RightType>) , "Result must have no reference alternative"); static_assert(!(std::is_void_v<LeftType> || std::is_void_v<RightType>) , "Result must have no void alternative"); using LeftValue = Left<LeftType>; using RightValue = Right<RightType>; static constexpr size_t index_left_ = 0; static constexpr size_t index_right_ = 1; const std::variant<const LeftValue, const RightValue> variant_; constexpr explicit Result(Result<LeftType, RightType> && other) : variant_ { std::forward<Result<LeftType, RightType>>( other ).variant_ } {} template<typename ParamType> constexpr explicit Result(ParamType const & value) : variant_ { []() -> auto { if constexpr (std::is_same_v<LeftType, ParamType> || is_left_v<ParamType>) { return std::in_place_index<index_left_>; } else if constexpr (std::is_same_v<RightType, ParamType> || is_right_v<ParamType>) { return std::in_place_index<index_right_>; } }() , [](ParamType const & value) -> auto { if constexpr (std::is_same_v<LeftType, ParamType>) { return to_left(value); } else if constexpr (std::is_same_v<RightType, ParamType>) { return to_right(value); } else if constexpr (is_left_v<ParamType> || is_right_v<ParamType>) { return value; } }(value) } { static_assert((is_left_v<ParamType> || is_right_v<ParamType> || std::is_same_v<LeftType, ParamType> || std::is_same_v<RightType, ParamType>) , "Result only setted alternatives can use"); if constexpr (!(is_left_v<ParamType> || is_right_v<ParamType>)) { static_assert(!std::is_same_v<LeftType, RightType> , "Result must have distinguish between alternatives"); } } template<typename ParamType> constexpr explicit Result(ParamType && value) noexcept : variant_ { []() -> auto { if constexpr (std::is_same_v<LeftType, ParamType> || is_left_v<ParamType>) { return std::in_place_index<index_left_>; } else if constexpr (std::is_same_v<RightType, ParamType> || is_right_v<ParamType>) { return std::in_place_index<index_right_>; } }() , [](ParamType && value) -> auto { if constexpr (std::is_same_v<LeftType, ParamType>) { return to_left(std::forward<ParamType>(value)); } else if constexpr (std::is_same_v<RightType, ParamType>) { return to_right(std::forward<ParamType>(value)); } else if constexpr (is_left_v<ParamType> || is_right_v<ParamType>) { return std::forward<ParamType>(value); } }(std::forward<ParamType>(value)) } { static_assert((is_left_v<ParamType> || is_right_v<ParamType> || std::is_same_v<LeftType, ParamType> || std::is_same_v<RightType, ParamType>) , "Result only setted alternatives can use"); if constexpr (!(is_left_v<ParamType> || is_right_v<ParamType>)) { static_assert(!std::is_same_v<LeftType, RightType> , "Result must have distinguish between alternatives"); } } template<typename TempType = LeftType> inline constexpr TempType const & left() const & { static_assert(std::is_convertible_v<TempType, LeftType>); return std::get<index_left_>(variant_).value_; } template<typename TempType = LeftType> constexpr TempType && left() && { static_assert(std::is_convertible_v<TempType &&, LeftType>); return std::move(std::get<index_left_>(variant_).value_); } template<typename TempType = LeftType> constexpr LeftType left_or(TempType && substitute) const & { static_assert(std::is_convertible_v<TempType &&, LeftType>); return std::holds_alternative<const LeftValue>(variant_) ? this->left() : static_cast<LeftType>(std::forward<TempType>(substitute)); } template<typename TempType = LeftType> constexpr LeftType && left_or(TempType && substitute) && { static_assert(std::is_convertible_v<TempType &&, LeftType>); return std::holds_alternative<const LeftValue>(variant_) ? std::move(this->left()) : static_cast<LeftType>(std::forward<TempType>(substitute)); } template<typename TempType = RightType> inline constexpr TempType const & right() const & { static_assert(std::is_convertible_v<TempType, RightType>); return std::get<index_right_>(variant_).value_; } template<typename TempType = RightType> constexpr TempType && right() && { static_assert(std::is_convertible_v<TempType &&, RightType>); return std::move(std::get<index_right_>(variant_).value_); } template<typename TempType = RightType> constexpr RightType right_or(TempType && substitute) const & { static_assert(std::is_convertible_v<TempType &&, RightType>); return std::holds_alternative<const LeftValue>(variant_) ? static_cast<RightType>(std::forward<TempType>(substitute)) : this->right(); } template<typename TempType = RightType> constexpr RightType && right_or(TempType && substitute) && { static_assert(std::is_convertible_v<TempType &&, RightType>); return std::holds_alternative<const LeftValue>(variant_) ? static_cast<RightType>(std::forward<TempType>(substitute)) : std::move(this->right()); } template<typename Function> inline constexpr auto left_map(Function const & function) && -> Result<decltype(function(std::get<index_left_>(variant_).value_)), RightType> { return std::holds_alternative<const LeftValue>(variant_) ? Result{ to_left(function(this->left())) } : Result{ std::get<index_right_>(variant_) }; } template<typename Function> inline constexpr auto right_map(Function const & function) const -> Result<LeftType, decltype(function(std::get<index_right_>(variant_).value_))> { return std::holds_alternative<const LeftValue>(variant_) ? Result{ std::get<index_left_>(variant_) } : Result{ to_right(function(this->right())) }; } template<typename LeftLocal = LeftType, typename RightLocal = RightType> inline constexpr auto join() const -> std::common_type_t<const LeftLocal, const RightLocal> { return std::holds_alternative<const LeftValue>(variant_) ? this->left() : this->right(); } inline constexpr operator bool() const noexcept { return std::holds_alternative<const LeftValue>(variant_); } }; } // namespace marklar::result
main.cpp
#include <iostream> #include <string> #include "result.hpp" // Tester function auto tester(int result) { using R = marklar::result::Result<int, std::string>; return (result < 0) ? R( marklar::result::to_right<std::string>("It is a negative number") ) : R( marklar::result::to_left<int>(result) ) ; } int main() { std::cout << std::boolalpha; std::cout << "Positive test\n"; auto resOk = tester(42); if(resOk) { std::cout << "data : " << std::to_string(resOk.left()) << "\n"; } else { std::cout << "error : " << resOk.right() << "\n"; } std::cout << std::endl; std::cout << "Negative test\n"; auto resErr = tester(-1); if(resErr) { std::cout << "data : " << std::to_string(resErr.left()) << "\n"; } else { std::cout << "error : " << resErr.right() << "\n"; } std::cout << std::endl; std::cout << "Same type test - lef side\n"; marklar::result::Result<int, int> resSameLeft(marklar::result::to_left(42)); std::cout << "Is store left data? : " << static_cast<bool>(resSameLeft) << "\n"; std::cout << "data : " << std::to_string(resSameLeft.left()) << "\n"; std::cout << std::endl; std::cout << "Same type test - right side\n"; marklar::result::Result<int, int> resSameRight(marklar::result::to_right(24)); std::cout << "Is store left data? : " << static_cast<bool>(resSameRight) << "\n"; std::cout << "data : " << std::to_string(resSameRight.right()) << "\n"; std::cout << std::endl; return 0; }
My questions:
- Any suggestion for better implementation?
- Is the perfect forwarding correctly used?
- Can be improve the usability?
main
so we can run your code without making changes. You are also missing#include
statements.\$\endgroup\$variant
? What's wrong with exceptions?\$\endgroup\$Result
, and makingLeft
andRight
more widely applicable. Simply usestd::variant
directly, and add a template encoding a statically chosen option from astd::variant
. Perhaps a few convenience aliases and/or functions, and you are done.\$\endgroup\$