diff --git a/source/simple/support/array_operators.hpp b/source/simple/support/array_operators.hpp index b2345975ab1aab061dd92a7332af7fa821146839..bf2ed96b2460ad2feb0b1db4c15c4a1ac3f740b8 100644 --- a/source/simple/support/array_operators.hpp +++ b/source/simple/support/array_operators.hpp @@ -42,117 +42,123 @@ namespace simple::support all = std::numeric_limits<std::underlying_type_t<array_operator>>::max(), binary = add | mul | sub | div | mod | bit_and | bit_or | bit_xor | lshift | rshift, unary = bit_not | negate, + bitwise = bit_and | bit_and_eq | bit_or | bit_or_eq | bit_xor | bit_xor_eq | bit_not, in_place = all ^ (binary | unary) }; template<> struct define_enum_flags_operators<array_operator> : public std::true_type {}; - template <typename T1, typename T2 = T1, - typename Result = decltype(std::declval<T1&>() += std::declval<const T2&>())> struct add_in_place { + template <typename T1, typename T2 = T1, + typename Result = decltype(std::declval<T1&>() += std::declval<const T2&>())> constexpr T1& operator()(T1& one, const T2& other) const { return one += other; } }; - template <typename T1, typename T2 = T1, - typename Result = decltype(std::declval<T1&>() -= std::declval<const T2&>()) > struct sub_in_place { + template <typename T1, typename T2 = T1, + typename Result = decltype(std::declval<T1&>() -= std::declval<const T2&>()) > constexpr T1& operator()(T1& one, const T2& other) const { return one -= other; } }; - template <typename T1, typename T2 = T1, - typename Result = decltype(std::declval<T1&>() *= std::declval<const T2&>())> struct mul_in_place { + template <typename T1, typename T2 = T1, + typename Result = decltype(std::declval<T1&>() *= std::declval<const T2&>())> constexpr T1& operator()(T1& one, const T2& other) const { return one *= other; } }; - template <typename T1, typename T2 = T1, - typename Result = decltype(std::declval<T1&>() /= std::declval<const T2&>())> struct div_in_place { + template <typename T1, typename T2 = T1, + typename Result = decltype(std::declval<T1&>() /= std::declval<const T2&>())> constexpr T1& operator()(T1& one, const T2& other) const { return one /= other; } }; - template <typename T1, typename T2 = T1> struct mod_in_place { - template <typename TT1 = T1, typename TT2 = T2, + template <typename T1, typename T2, std::enable_if_t< - std::is_floating_point_v<TT1> && std::is_floating_point_v<TT2> + std::is_floating_point_v<T1> && std::is_floating_point_v<T2> > * = nullptr, - typename Result = decltype(std::declval<TT1&>() = - std::fmod(std::declval<TT1&>(), std::declval<const TT2&>()) + typename Result = decltype(std::declval<T1&>() = + std::fmod(std::declval<T1&>(), std::declval<const T2&>()) ) > - constexpr TT1& operator()(TT1& one, const TT2& other) const + constexpr T1& operator()(T1& one, const T2& other) const { return one = std::fmod(one, other); } - template <typename TT1 = T1, typename TT2 = T2, + template <typename T1, typename T2, std::enable_if_t< - not(std::is_floating_point_v<TT1> && std::is_floating_point_v<TT2>) + not(std::is_floating_point_v<T1> && std::is_floating_point_v<T2>) > * = nullptr, - typename Result = decltype(std::declval<TT1&>() %= std::declval<const TT2&>()) + typename Result = decltype(std::declval<T1&>() %= std::declval<const T2&>()) > - constexpr TT1& operator()(TT1& one, const TT2& other) const + constexpr T1& operator()(T1& one, const T2& other) const { return one %= other; } }; - template <typename T1, typename T2 = T1, typename Result = - decltype(std::declval<const T1&>() + std::declval<const T2&>())> + // TODO: got an opportunity to counter some insane fundamental integral promotion rules here + // bool bit_op bool = int // bool is guaranteed to be 0 or 1 so logical bit op results can fit in bool, not to mention bit twiddling an int is implementation defined + // (unsigned short) * (unsigned short) = int // overflows for large values, which is undefined behavior for int, should promote to unsigned instead, can't guarantee no overflow, but at least not freakin UB + // there is no reason for unary plus to promote, other than if it's used as some sort of explicit promotion operator, but I don't really like that, since proper promotion rules should be based on operators and not just types. in my eyes it unary plus should be just a shorthand for copy construction + // // that said it might be better to leave these kind of descisions to the element type instead, but the defaults are just so brokeeeeen T_T the temptatioooon ToT + + struct add { + template <typename T1, typename T2 = T1, typename Result = + decltype(std::declval<const T1&>() + std::declval<const T2&>())> constexpr Result operator()(const T1& one, const T2& other) const { return one + other; } }; - template <typename T1, typename T2 = T1, typename Result = - decltype(std::declval<const T1&>() - std::declval<const T2&>())> struct sub { + template <typename T1, typename T2 = T1, typename Result = + decltype(std::declval<const T1&>() - std::declval<const T2&>())> constexpr Result operator()(const T1& one, const T2& other) const { return one - other; } }; - template <typename T1, typename T2 = T1, typename Result = - decltype(std::declval<const T1&>() * std::declval<const T2&>())> struct mul { + template <typename T1, typename T2 = T1, typename Result = + decltype(std::declval<const T1&>() * std::declval<const T2&>())> constexpr Result operator()(const T1& one, const T2& other) const { return one * other; } }; - template <typename T1, typename T2 = T1, typename Result = - decltype(std::declval<const T1&>() / std::declval<const T2&>())> struct div { + template <typename T1, typename T2 = T1, typename Result = + decltype(std::declval<const T1&>() / std::declval<const T2&>())> constexpr Result operator()(const T1& one, const T2& other) const { return one / other; } }; - template <typename T1, typename T2 = T1> struct mod { - template <typename TT1 = T1, typename TT2 = T2, + template <typename T1, typename T2, std::enable_if_t< - std::is_floating_point_v<TT1> && std::is_floating_point_v<TT2> + std::is_floating_point_v<T1> && std::is_floating_point_v<T2> > * = nullptr, typename Result = decltype( - std::fmod(std::declval<const TT1&>(), std::declval<const TT2&>()) + std::fmod(std::declval<const T1&>(), std::declval<const T2&>()) ) > constexpr Result operator()(const T1& one, const T2& other) const { return std::fmod(one, other); } - template <typename TT1 = T1, typename TT2 = T2, + template <typename T1, typename T2, std::enable_if_t< - not(std::is_floating_point_v<TT1> && std::is_floating_point_v<TT2>) + not(std::is_floating_point_v<T1> && std::is_floating_point_v<T2>) > * = nullptr, typename Result = decltype( - std::declval<const TT1&>() % std::declval<const TT2&>() + std::declval<const T1&>() % std::declval<const T2&>() ) > constexpr Result operator()(const T1& one, const T2& other) const @@ -161,113 +167,159 @@ namespace simple::support } }; - template <typename T1, typename T2 = T1, - typename Result = decltype(std::declval<T1&>() &= std::declval<const T2&>())> struct bit_and_in_place { + template <typename T1, typename T2 = T1, + typename Result = decltype(std::declval<T1&>() &= std::declval<const T2&>())> constexpr T1& operator()(T1& one, const T2& other) const { return one &= other; } }; - template <typename T1, typename T2 = T1, - typename Result = decltype(std::declval<T1&>() |= std::declval<const T2&>())> struct bit_or_in_place { + template <typename T1, typename T2 = T1, + typename Result = decltype(std::declval<T1&>() |= std::declval<const T2&>())> constexpr T1& operator()(T1& one, const T2& other) const { return one |= other; } }; - template <typename T1, typename T2 = T1, - typename Result = decltype(std::declval<T1&>() ^= std::declval<const T2&>())> struct bit_xor_in_place { + template <typename T1, typename T2 = T1, + typename Result = decltype(std::declval<T1&>() ^= std::declval<const T2&>())> constexpr T1& operator()(T1& one, const T2& other) const { return one ^= other; } }; - template <typename T1, typename T2 = T1, typename Result = - decltype(std::declval<const T1&>() & std::declval<const T2&>())> struct bit_and { + template <typename T1, typename T2 = T1, typename Result = + decltype(std::declval<const T1&>() & std::declval<const T2&>())> constexpr Result operator()(const T1& one, const T2& other) const { return one & other; } }; - template <typename T1, typename T2 = T1, typename Result = - decltype(std::declval<const T1&>() | std::declval<const T2&>())> struct bit_or { + template <typename T1, typename T2 = T1, typename Result = + decltype(std::declval<const T1&>() | std::declval<const T2&>())> constexpr Result operator()(const T1& one, const T2& other) const { return one | other; } }; - template <typename T1, typename T2 = T1, typename Result = - decltype(std::declval<const T1&>() ^ std::declval<const T2&>())> struct bit_xor { + template <typename T1, typename T2 = T1, typename Result = + decltype(std::declval<const T1&>() ^ std::declval<const T2&>())> constexpr Result operator()(const T1& one, const T2& other) const { return one ^ other; } }; - template <typename T1, typename T2 = T1, typename Result = - decltype(std::declval<const T1&>() << std::declval<const T2&>())> struct lshift { + template <typename T1, typename T2 = T1, typename Result = + decltype(std::declval<const T1&>() << std::declval<const T2&>())> constexpr Result operator()(const T1& one, const T2& other) const { return one << other; } }; - template <typename T1, typename T2 = T1, typename Result = - decltype(std::declval<const T1&>() >> std::declval<const T2&>())> struct rshift { + template <typename T1, typename T2 = T1, typename Result = + decltype(std::declval<const T1&>() >> std::declval<const T2&>())> constexpr Result operator()(const T1& one, const T2& other) const { return one >> other; } }; - template <typename T1, typename T2 = T1, - typename Result = decltype(std::declval<T1&>() <<= std::declval<const T2&>())> struct lshift_in_place { + template <typename T1, typename T2 = T1, + typename Result = decltype(std::declval<T1&>() <<= std::declval<const T2&>())> constexpr T1& operator()(T1& one, const T2& other) const { return one <<= other; } }; - template <typename T1, typename T2 = T1, - typename Result = decltype(std::declval<T1&>() >>= std::declval<const T2&>())> struct rshift_in_place { + template <typename T1, typename T2 = T1, + typename Result = decltype(std::declval<T1&>() >>= std::declval<const T2&>())> constexpr T1& operator()(T1& one, const T2& other) const { return one >>= other; } }; - template <typename Type, typename Result = decltype(~std::declval<const Type&>())> struct bit_not { + template <typename Type, typename Result = decltype(~std::declval<const Type&>())> constexpr Result operator()(const Type& one) const { return ~one; } }; - // public + struct negate + { + template <typename Type, typename Result = decltype(-std::declval<const Type&>())> + constexpr Result operator()(const Type& one) const { return -one; } + }; + + // public trait to specialize template <typename Type> struct define_array_operators { + // specify the size of the array for the purposes of these operators constexpr static size_t size = 0; + + // a way to get to an actual array-like type in case Type does not expose the interface constexpr static auto& get_array(Type& object); + + // operators to enable, only considering operands that define this trait constexpr static array_operator enabled_operators = array_operator::none; + // binary and in_place operators to enable, considering element types on the right side constexpr static array_operator enabled_right_element_operators = array_operator::none; + // binary and in_place operators to enable, considering element types on the left side constexpr static array_operator enabled_left_element_operators = array_operator::none; + + // this template is used to change the element type of the array + // in context of deducing the result of binary operations, + // if your array-like type is templated on the element type you can wire it through here + // to support type promotion, expression templates or other special techniques (like boost safe_numerics) + // Result - the deduced result element type + // array_operator - the operator in question + // Other - the element type of the other operand (for unary ops it's same as Type) + template <typename Result, array_operator, typename Other = Type> + using result = Type; + + // if this type is the same between two types they will be considered as operands for binary and in-place operators, + // as long as corresponding element types support the operator, + // this is the another thing you need in order to support type promotion, expression templates etc. + // you can set this to the shape of the array (which would be size if there is no nesting), + // along with a tag that identifies the template, if you want that extra safety + using compatibility_tag = Type; }; + // public trait to use + // some common defaults for defining array operators on a type template <typename Type, size_t Size> struct trivial_array_accessor { - constexpr static size_t size = Size; - constexpr static Type& get_array(Type& object) noexcept { return object; } - constexpr static const Type& get_array(const Type& object) noexcept { return object; } + constexpr static size_t size = Size; // explicitly specified size + + // the type provides array-like interface itself, no digging or wrapping necessary + template <typename T> + constexpr static T& get_array(T& object) noexcept { return object; } + template <typename T> + constexpr static const T& get_array(const T& object) noexcept { return object; } + + template <typename Result, array_operator, typename Other = Type> + using result = Type; // the result of binary operators is the same exact type, in all cases + // note that, while it can help somewhat, this does not (and can not) technically prevent type promotion and associated problems, + // to get around that properly you would need a more sophisticated solution + // see https://www.boost.org/doc/libs/1_75_0/libs/safe_numerics/doc/html/promotion_policy.html + + using compatibility_tag = Type; // operands of binary and in-place operators must be the same exact type }; - // public - template <typename T> - struct array_operator_implicit_conversion { using type = support::remove_cvref_t<T>; }; - template <typename T> + // public trait to specialize + // allows you to specify an implicit conversion to be considered by binary and in-place operators, + // since templates do not considered conversions otherwise + template <typename From> + struct array_operator_implicit_conversion { using type = support::remove_cvref_t<From>; }; + template <typename From> using array_operator_implicit_conversion_t = - typename array_operator_implicit_conversion<support::remove_cvref_t<T>>::type; + typename array_operator_implicit_conversion<support::remove_cvref_t<From>>::type; namespace AOps_Details { - template <typename Operator, size_t Size, typename Type> - constexpr Type& array_unary_operator(Type& result, const Type& one) + template <typename Operator, size_t Size, typename Type, typename Result> + constexpr Result& array_unary_operator(Result& result, const Type& one) { auto op = Operator{}; for(size_t i = 0; i < Size; ++i) @@ -275,8 +327,8 @@ namespace AOps_Details return result; } - template <typename Operator, size_t Size, typename Type> - constexpr Type& array_in_place_operator(Type& one, const Type& other) + template <typename Operator, size_t Size, typename Type, typename Other> + constexpr Type& array_in_place_operator(Type& one, const Other& other) { auto op = Operator{}; for(size_t i = 0; i < Size; ++i) @@ -302,8 +354,8 @@ namespace AOps_Details return element; } - template <typename Operator, size_t Size, typename Type> - constexpr Type& array_binary_operator(Type& result, const Type& one, const Type& other) + template <typename Operator, size_t Size, typename One, typename Other, typename Result> + constexpr Result& array_binary_operator(Result& result, const One& one, const Other& other) { auto op = Operator{}; for(size_t i = 0; i < Size; ++i) @@ -343,50 +395,60 @@ namespace AOps_Details template <typename Array, \ typename OperatorDef = simple::support::define_array_operators<Array>, \ std::enable_if_t<OperatorDef::enabled_operators && simple::support::array_operator::op_type>* = nullptr> \ -[[nodiscard]] constexpr Array operator op_symbol (const Array& one) \ +[[nodiscard]] constexpr auto operator op_symbol (const Array& one) \ { \ using namespace simple::support; \ using namespace simple::support::AOps_Details; \ - Array result{}; \ + using element_t = simple::support::AOps_Details::array_element_t<OperatorDef, Array>; \ + using result_element_t = std::invoke_result_t<op_fun,element_t>; \ + using result_t = typename OperatorDef::template result<result_element_t, simple::support::array_operator::op_type, element_t>; \ + result_t result{}; \ array_unary_operator \ - <op_fun<array_element_t<OperatorDef, Array>>, OperatorDef::size> \ + <op_fun, OperatorDef::size> \ (OperatorDef::get_array(result), OperatorDef::get_array(one)); \ return result; \ } SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR(~, bit_not, simple::support::bit_not) -SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR(-, negate, std::negate) +SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR(-, negate, simple::support::negate) #undef SIMPLE_SUPPORT_DEFINE_UNARY_OPERATOR #define SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(op_symbol, op_type, op_fun) \ -template <typename Array, void* = nullptr, \ +template <typename Array, typename Other, void* = nullptr, \ typename OperatorDef = simple::support::define_array_operators<Array>, \ - std::enable_if_t<OperatorDef::enabled_operators && simple::support::array_operator::op_type>* = nullptr> \ -[[nodiscard]] constexpr Array operator op_symbol (const Array& one, const Array& other) \ + typename OtherOperatorDef = simple::support::define_array_operators<Other>, \ + typename Element = simple::support::AOps_Details::array_element_t<OperatorDef, Array>, \ + typename OtherElement = simple::support::AOps_Details::array_element_t<OtherOperatorDef, Other>, \ + std::enable_if_t<OperatorDef::enabled_operators && simple::support::array_operator::op_type>* = nullptr, \ + std::enable_if_t<OtherOperatorDef::enabled_operators && simple::support::array_operator::op_type>* = nullptr, \ + std::enable_if_t<std::is_same_v<typename OtherOperatorDef::compatibility_tag, typename OperatorDef::compatibility_tag>>* = nullptr, \ + std::enable_if_t<std::is_invocable_v<op_fun,Element,OtherElement>>* = nullptr> \ +[[nodiscard]] constexpr auto operator op_symbol (const Array& one, const Other& other) \ { \ using namespace simple::support; \ using namespace simple::support::AOps_Details; \ - Array result{}; \ + using result_t = typename OperatorDef::template result<std::invoke_result_t<op_fun,Element,OtherElement>, simple::support::array_operator::op_type, OtherElement>; \ + result_t result{}; \ + using result_op_def = simple::support::define_array_operators<result_t>; \ + static_assert(result_op_def::size == OperatorDef::size); \ array_binary_operator \ - <op_fun<array_element_t<OperatorDef, Array>>, OperatorDef::size> \ - (OperatorDef::get_array(result), OperatorDef::get_array(one), OperatorDef::get_array(other)); \ + <op_fun, OperatorDef::size> \ + (result_op_def::get_array(result), OperatorDef::get_array(one), OtherOperatorDef::get_array(other)); \ return result; \ } \ \ template <typename T1, typename T2, \ typename C1 = simple::support::array_operator_implicit_conversion_t<T1>, \ typename C2 = simple::support::array_operator_implicit_conversion_t<T2>, \ - typename OperatorDef = simple::support::define_array_operators<C1>, \ std::enable_if_t< \ - (OperatorDef::enabled_operators && simple::support::array_operator::op_type) \ - && std::is_same_v<C1,C2> \ - && (!std::is_same_v<T1,C1> || !std::is_same_v<T2,C2>) \ + (!std::is_same_v<T1,C1> || !std::is_same_v<T2,C2>) \ >* = nullptr \ > \ [[nodiscard]] constexpr auto operator op_symbol (T1&& one, T2&& other) \ + -> decltype(operator op_symbol<C1,C2,nullptr>(std::forward<T1>(one), std::forward<T2>(other))) \ { \ - return operator op_symbol<C1,nullptr>(std::forward<T1>(one), std::forward<T2>(other)); \ + return operator op_symbol<C1,C2,nullptr>(std::forward<T1>(one), std::forward<T2>(other)); \ } \ \ template <typename Array, typename Other, \ @@ -394,7 +456,7 @@ template <typename Array, typename Other, \ typename Element = simple::support::AOps_Details::array_element_t<OperatorDef, Array>, \ std::enable_if_t<(OperatorDef::enabled_right_element_operators && simple::support::array_operator::op_type) \ && !std::is_same_v<Array, Other> \ - && std::is_invocable_r_v<Element, op_fun<Element,Other>, Element, Other>\ + && std::is_invocable_r_v<Element, op_fun, Element, Other>\ >* = nullptr \ > \ [[nodiscard]] constexpr Array operator op_symbol \ @@ -407,7 +469,7 @@ template <typename Array, typename Other, \ using namespace simple::support::AOps_Details; \ Array result{}; \ array_right_element_binary_operator \ - <op_fun<Element,Other>, OperatorDef::size> \ + <op_fun, OperatorDef::size> \ (OperatorDef::get_array(result), OperatorDef::get_array(one), other); \ return result; \ } \ @@ -417,7 +479,7 @@ template <typename Array, typename Other, \ typename Element = simple::support::AOps_Details::array_element_t<OperatorDef, Array>, \ std::enable_if_t<(OperatorDef::enabled_left_element_operators && simple::support::array_operator::op_type) \ && !std::is_same_v<Array, Other> \ - && std::is_invocable_r_v<Element, op_fun<Other,Element>, Other, Element>\ + && std::is_invocable_r_v<Element, op_fun, Other, Element>\ >* = nullptr \ > \ [[nodiscard]] constexpr Array operator op_symbol \ @@ -430,7 +492,7 @@ template <typename Array, typename Other, \ using namespace simple::support::AOps_Details; \ Array result{}; \ array_left_element_binary_operator \ - <op_fun<Other,Element>, OperatorDef::size> \ + <op_fun, OperatorDef::size> \ (OperatorDef::get_array(result), OperatorDef::get_array(other), one); \ return result; \ } @@ -449,28 +511,47 @@ SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR(>>, rshift, simple::support::rshift) #undef SIMPLE_SUPPORT_DEFINE_BINARY_OPERATOR #define SIMPLE_SUPPORT_DEFINE_IN_PLACE_OPERATOR(op_symbol, op_type, op_fun) \ -template <typename Array, \ +template <typename Array, typename Other, void* = nullptr, \ typename OperatorDef = simple::support::define_array_operators<Array>, \ - std::enable_if_t<OperatorDef::enabled_operators && simple::support::array_operator::op_type>* = nullptr> \ + typename OtherOperatorDef = simple::support::define_array_operators<Other>, \ + typename Element = simple::support::AOps_Details::array_element_t<OperatorDef, Array>, \ + typename OtherElement = simple::support::AOps_Details::array_element_t<OtherOperatorDef, Other>, \ + std::enable_if_t<OperatorDef::enabled_operators && simple::support::array_operator::op_type>* = nullptr, \ + std::enable_if_t<OtherOperatorDef::enabled_operators && simple::support::array_operator::op_type>* = nullptr, \ + std::enable_if_t<std::is_same_v<typename OtherOperatorDef::compatibility_tag, typename OperatorDef::compatibility_tag>>* = nullptr, \ + std::enable_if_t<std::is_invocable_v<op_fun,Element&,const OtherElement&>>* = nullptr> \ constexpr Array& operator op_symbol \ ( \ Array& one, \ - const simple::support::non_deduced<Array>& other \ + const Other& other \ ) \ { \ using namespace simple::support; \ using namespace simple::support::AOps_Details; \ array_in_place_operator \ - <op_fun<array_element_t<OperatorDef, Array>>, OperatorDef::size> \ - (OperatorDef::get_array(one), OperatorDef::get_array(other)); \ + <op_fun, OperatorDef::size> \ + (OperatorDef::get_array(one), OtherOperatorDef::get_array(other)); \ return one; \ } \ +\ +template <typename Array, typename Other, \ + typename OtherConv = simple::support::array_operator_implicit_conversion_t<Other>, \ + std::enable_if_t< \ + (!std::is_same_v<OtherConv,Other>) \ + >* = nullptr \ +> \ +constexpr auto operator op_symbol (Array& one, const Other& other) \ + -> decltype(operator op_symbol<Array,OtherConv,nullptr>(one, other)) \ +{ \ + return operator op_symbol<Array,OtherConv,nullptr>(one, other); \ +} \ +\ template <typename Array, typename Other, \ typename OperatorDef = simple::support::define_array_operators<Array>, \ typename Element = simple::support::AOps_Details::array_element_t<OperatorDef, Array>, \ std::enable_if_t<(OperatorDef::enabled_right_element_operators && simple::support::array_operator::op_type) \ && !std::is_same_v<Array, Other> \ - && std::is_invocable_r_v<Element&, op_fun<Element, Other>, Element&, Other> \ + && std::is_invocable_r_v<Element&, op_fun, Element&, Other> \ >* = nullptr \ > \ constexpr Array& operator op_symbol \ @@ -482,7 +563,7 @@ constexpr Array& operator op_symbol \ using namespace simple::support; \ using namespace simple::support::AOps_Details; \ array_right_element_in_place_operator \ - <op_fun<Element,Other>, OperatorDef::size> \ + <op_fun, OperatorDef::size> \ (OperatorDef::get_array(one), other); \ return one; \ } \ @@ -491,7 +572,7 @@ template <typename Array, typename Other, \ typename Element = simple::support::AOps_Details::array_element_t<OperatorDef, Array>, \ std::enable_if_t<(OperatorDef::enabled_left_element_operators && simple::support::array_operator::op_type) \ && !std::is_same_v<Array, Other> \ - && std::is_invocable_r_v<Element&, op_fun<Other,Element>, Other&, Element> \ + && std::is_invocable_r_v<Element&, op_fun, Other&, Element> \ >* = nullptr \ > \ constexpr Other& operator op_symbol \ @@ -503,7 +584,7 @@ constexpr Other& operator op_symbol \ using namespace simple::support; \ using namespace simple::support::AOps_Details; \ array_left_element_in_place_operator \ - <op_fun<Other,Element>, OperatorDef::size> \ + <op_fun, OperatorDef::size> \ (one, OperatorDef::get_array(other)); \ return one; \ } diff --git a/source/simple/support/rational.hpp b/source/simple/support/rational.hpp index 805871053587ddd0bd38e25e86f5b5ea20373c60..8e3f405bf1b4df35c1b0510cd53bcfb0c90df850 100644 --- a/source/simple/support/rational.hpp +++ b/source/simple/support/rational.hpp @@ -3,6 +3,7 @@ #include "array.hpp" #include "array_operators.hpp" +#include "type_traits.hpp" namespace simple::support { @@ -23,21 +24,21 @@ namespace simple::support return *this; } - constexpr operator Int() const + constexpr explicit operator Int() const { return (*this)[numerator] / (*this)[denominator]; } }; template <typename Int> - constexpr rational<Int> operator*(rational<Int> ratio, const Int& value) + constexpr rational<Int> operator*(rational<Int> ratio, const non_deduced<Int>& value) { ratio *= value; return ratio; } template <typename Int> - constexpr rational<Int> operator*(const Int& value, rational<Int> ratio) + constexpr rational<Int> operator*(const non_deduced<Int>& value, rational<Int> ratio) { ratio *= value; return ratio; diff --git a/unit_tests/rational.cpp b/unit_tests/rational.cpp index 31b59db8d298c314eb32d5d03889c89826872efb..8c7333ac713e705410d507fa941f3352a7f6e0a6 100644 --- a/unit_tests/rational.cpp +++ b/unit_tests/rational.cpp @@ -4,27 +4,27 @@ using simple::support::rational; constexpr bool Ratio() { - static_assert(10 * rational{1,2} == 5); - static_assert(9 * rational{1,2} == 4); - static_assert(9 * rational{1.0,2.0} == 4.5); - static_assert(rational{1,3} * 9 == 3); - static_assert(rational{1,3} * rational{5,2} * 12 == 10); - static_assert(13 * rational{5,2} * rational{2,5} == 13); + static_assert(int(10 * rational{1,2}) == 5); + static_assert(int(9 * rational{1,2}) == 4); + static_assert(double(9 * rational{1.0,2.0}) == 4.5); + static_assert(int(rational{1,3} * 9) == 3); + static_assert(int(rational{1,3} * rational{5,2} * 12) == 10); + static_assert(int(13 * rational{5,2} * rational{2,5}) == 13); bool assertion = true; auto ratio = rational{1,2}; - assertion &= ratio == 0; + assertion &= int(ratio) == 0; ratio *= 2; - assertion &= ratio == 1; + assertion &= int(ratio) == 1; ratio *= 3; - assertion &= ratio == 3; + assertion &= int(ratio) == 3; ratio *= rational{1,2}; - assertion &= ratio == 1; + assertion &= int(ratio) == 1; ratio *= 5; - assertion &= ratio == 7; + assertion &= int(ratio) == 7; ratio *= 2; - assertion &= ratio == 15; + assertion &= int(ratio) == 15; return assertion; }