diff --git a/au/code/au/apply_magnitude.hh b/au/code/au/apply_magnitude.hh index e0c580e9..74353a4b 100644 --- a/au/code/au/apply_magnitude.hh +++ b/au/code/au/apply_magnitude.hh @@ -103,7 +103,7 @@ struct ApplyMagnitudeImpl { static_assert(is_T_integral == std::is_integral::value, "Mismatched instantiation (should never be done manually)"); - constexpr T operator()(const T &x) { return x * get_value(Mag{}); } + constexpr T operator()(const T &x) { return x * get_value>(Mag{}); } static constexpr bool would_overflow(const T &x) { constexpr auto mag_value_result = get_value_result(Mag{}); @@ -122,7 +122,7 @@ struct ApplyMagnitudeImpl { static_assert(is_T_integral == std::is_integral::value, "Mismatched instantiation (should never be done manually)"); - constexpr T operator()(const T &x) { return x / get_value(MagInverseT{}); } + constexpr T operator()(const T &x) { return x / get_value>(MagInverseT{}); } static constexpr bool would_overflow(const T &) { return false; } @@ -165,8 +165,8 @@ struct ApplyMagnitudeImpl { constexpr T operator()(const T &x) { using P = PromotedType; - return static_cast(x * get_value

(numerator(Mag{})) / - get_value

(denominator(Mag{}))); + return static_cast(x * get_value>(numerator(Mag{})) / + get_value>(denominator(Mag{}))); } static constexpr bool would_overflow(const T &x) { @@ -188,7 +188,7 @@ struct ApplyMagnitudeImpl { static_assert(!std::is_integral::value, "Mismatched instantiation (should never be done manually)"); - constexpr T operator()(const T &x) { return x * get_value(Mag{}); } + constexpr T operator()(const T &x) { return x * get_value>(Mag{}); } static constexpr bool would_overflow(const T &x) { constexpr auto mag_value_result = get_value_result(Mag{}); @@ -209,7 +209,7 @@ struct ApplyMagnitudeImpl { static_assert(is_T_integral == std::is_integral::value, "Mismatched instantiation (should never be done manually)"); - constexpr T operator()(const T &x) { return x * get_value(Mag{}); } + constexpr T operator()(const T &x) { return x * get_value>(Mag{}); } static constexpr bool would_overflow(const T &x) { constexpr auto mag_value_result = get_value_result(Mag{}); diff --git a/au/code/au/conversion_policy.hh b/au/code/au/conversion_policy.hh index fe0de712..a48dbde1 100644 --- a/au/code/au/conversion_policy.hh +++ b/au/code/au/conversion_policy.hh @@ -52,7 +52,7 @@ template struct SameDimension : stdx::bool_constant {}; template -struct CoreImplicitConversionPolicyImpl +struct CoreImplicitConversionPolicyImplAssumingReal : stdx::disjunction< std::is_floating_point, stdx::conjunction, @@ -61,7 +61,24 @@ struct CoreImplicitConversionPolicyImpl // Always permit the identity scaling. template -struct CoreImplicitConversionPolicyImpl, Rep> : std::true_type {}; +struct CoreImplicitConversionPolicyImplAssumingReal, Rep> : std::true_type {}; + +// `SettingPureRealFromMixedReal` tests whether `A` is a pure real type, _and_ `B` is a type +// that has a real _part_, but is not purely real (call it a "mixed-real" type). +// +// The point is to guard against situations where we're _implicitly_ converting a "mixed-real" type +// (i.e., typically a complex number) to a pure real type. +template +struct SettingPureRealFromMixedReal + : stdx::conjunction>>, + std::is_same>> {}; + +template +struct CoreImplicitConversionPolicyImpl + : stdx::conjunction>, + CoreImplicitConversionPolicyImplAssumingReal, + ScaleFactor, + RealPart>> {}; template using CoreImplicitConversionPolicy = CoreImplicitConversionPolicyImpl; diff --git a/au/code/au/conversion_policy_test.cc b/au/code/au/conversion_policy_test.cc index b1dde31e..1ce2ded8 100644 --- a/au/code/au/conversion_policy_test.cc +++ b/au/code/au/conversion_policy_test.cc @@ -14,6 +14,8 @@ #include "au/conversion_policy.hh" +#include + #include "au/unit_of_measure.hh" #include "gtest/gtest.h" @@ -106,6 +108,17 @@ TEST(ImplicitRepPermitted, FunctionalInterfaceWorksAsExpected) { EXPECT_TRUE(implicit_rep_permitted_from_source_to_target(Grams{}, Kilograms{})); } +TEST(ImplicitRepPermitted, HandlesComplexRep) { + // These test cases are the same as the ones in `FunctionalInterfaceWorksAsExpected`, except + // that we replace the target type `T` with `std::complex`. + EXPECT_TRUE( + implicit_rep_permitted_from_source_to_target>(Kilograms{}, Grams{})); + EXPECT_FALSE( + implicit_rep_permitted_from_source_to_target>(Grams{}, Kilograms{})); + EXPECT_TRUE( + implicit_rep_permitted_from_source_to_target>(Grams{}, Kilograms{})); +} + TEST(ConstructionPolicy, PermitImplicitFromWideVarietyOfTypesForFloatingPointTargets) { using gigagrams_float_policy = ConstructionPolicy; EXPECT_TRUE((gigagrams_float_policy::PermitImplicitFrom::value)); @@ -126,6 +139,19 @@ TEST(ConstructionPolicy, PermitsImplicitFromIntegralTypesIffTargetScaleDividesSo EXPECT_FALSE((grams_int_policy::PermitImplicitFrom::value)); } +TEST(ConstructionPolicy, ComplexToRealPreventsImplicitConversion) { + // `complex` -> `float`: forbid, although `int` -> `float` is allowed. + using gigagrams_float_policy = ConstructionPolicy; + ASSERT_TRUE((gigagrams_float_policy::PermitImplicitFrom::value)); + EXPECT_FALSE((gigagrams_float_policy::PermitImplicitFrom>::value)); + + // (`int` or `complex`) -> `complex`: both allowed. + using gigagrams_complex_float_policy = ConstructionPolicy>; + EXPECT_TRUE((gigagrams_complex_float_policy::PermitImplicitFrom::value)); + EXPECT_TRUE( + (gigagrams_complex_float_policy::PermitImplicitFrom>::value)); +} + TEST(ConstructionPolicy, ForbidsImplicitConstructionOfIntegralTypeFromFloatingPtType) { using grams_int_policy = ConstructionPolicy; EXPECT_FALSE((grams_int_policy::PermitImplicitFrom::value)); diff --git a/au/code/au/magnitude.hh b/au/code/au/magnitude.hh index f93e3cbd..cfed3da3 100644 --- a/au/code/au/magnitude.hh +++ b/au/code/au/magnitude.hh @@ -15,6 +15,7 @@ #pragma once #include +#include #include "au/packs.hh" #include "au/power_aliases.hh" @@ -467,12 +468,26 @@ constexpr bool all(const bool (&values)[N]) { return true; } +// `RealPart` is `T` itself, unless that type has a `.real()` member. +template +using TypeOfRealMember = decltype(std::declval().real()); +template +// `RealPartImpl` is basically equivalent to the `detected_or` part at the +// end. But we special-case `is_arithmetic` to get a fast short-circuit for the overwhelmingly most +// common case. +struct RealPartImpl : std::conditional::value, + T, + stdx::experimental::detected_or_t> { +}; +template +using RealPart = typename RealPartImpl::type; + template struct SafeCastingChecker { template constexpr bool operator()(T x) { - return stdx::cmp_less_equal(std::numeric_limits::lowest(), x) && - stdx::cmp_greater_equal(std::numeric_limits::max(), x); + return stdx::cmp_less_equal(std::numeric_limits>::lowest(), x) && + stdx::cmp_greater_equal(std::numeric_limits>::max(), x); } }; @@ -481,8 +496,8 @@ struct SafeCastingChecker::val template constexpr bool operator()(T x) { return std::is_integral::value && - stdx::cmp_less_equal(std::numeric_limits::lowest(), x) && - stdx::cmp_greater_equal(std::numeric_limits::max(), x); + stdx::cmp_less_equal(std::numeric_limits>::lowest(), x) && + stdx::cmp_greater_equal(std::numeric_limits>::max(), x); } }; @@ -501,8 +516,8 @@ constexpr MagRepresentationOrError get_value_result(Magnitude) { } // Force the expression to be evaluated in a constexpr context. - constexpr auto widened_result = - product({base_power_value::num, static_cast(ExpT::den)>( + constexpr auto widened_result = product( + {base_power_value, ExpT::num, static_cast(ExpT::den)>( BaseT::value())...}); if ((widened_result.outcome != MagRepresentationOutcome::OK) || diff --git a/au/code/au/quantity.hh b/au/code/au/quantity.hh index 037f2164..a5723550 100644 --- a/au/code/au/quantity.hh +++ b/au/code/au/quantity.hh @@ -339,16 +339,21 @@ class Quantity { return *this; } - // Short-hand multiplication assignment. template - constexpr Quantity &operator*=(T s) { + constexpr void perform_shorthand_checks() { static_assert( - std::is_arithmetic::value, - "This overload is only for scalar multiplication-assignment with arithmetic types"); + IsValidRep::value, + "This overload is only for scalar mult/div-assignment with raw numeric types"); - static_assert( - std::is_floating_point::value || std::is_integral::value, - "We don't support compound multiplication of integral types by floating point"); + static_assert((!std::is_integral>::value) || + std::is_integral>::value, + "We don't support compound mult/div of integral types by floating point"); + } + + // Short-hand multiplication assignment. + template + constexpr Quantity &operator*=(T s) { + perform_shorthand_checks(); value_ *= s; return *this; @@ -357,11 +362,7 @@ class Quantity { // Short-hand division assignment. template constexpr Quantity &operator/=(T s) { - static_assert(std::is_arithmetic::value, - "This overload is only for scalar division-assignment with arithmetic types"); - - static_assert(std::is_floating_point::value || std::is_integral::value, - "We don't support compound division of integral types by floating point"); + perform_shorthand_checks(); value_ /= s; return *this; diff --git a/au/code/au/quantity_test.cc b/au/code/au/quantity_test.cc index 939b9ebc..e5f1d4e0 100644 --- a/au/code/au/quantity_test.cc +++ b/au/code/au/quantity_test.cc @@ -463,6 +463,40 @@ TEST(Quantity, SupportsDivisionOfRealQuantityIntoComplexCoefficient) { EXPECT_THAT(quotient.imag(), DoubleEq(4.0)); } +TEST(Quantity, SupportsConvertingUnitsForComplexQuantity) { + constexpr auto a = meters(std::complex{-3.0, 4.0}); + const auto b = a.as(centi(meters)); + EXPECT_THAT(b, SameTypeAndValue(centi(meters)(std::complex{-300.0, 400.0}))); +} + +TEST(Quantity, SupportsExplicitRepConversionToComplexRep) { + constexpr auto a = feet(15'000.0); + const auto b = a.as>(miles); + EXPECT_THAT(b, SameTypeAndValue(miles(std::complex{2, 0}))); +} + +TEST(Quantity, ShorthandMultiplicationAssignmentWorksForComplexRepAndScalar) { + auto test = meters(std::complex{1.5f, 0.5f}); + test *= std::complex{2.0f, 1.0f}; + EXPECT_THAT(test, SameTypeAndValue(meters(std::complex{2.5f, 2.5f}))); +} + +template +constexpr T double_by_shorthand(T x) { + return x *= 2.0; +} + +TEST(Quantity, ShorthandMultiplicationSupportsConstexpr) { + constexpr auto x = double_by_shorthand(feet(3.0)); + EXPECT_THAT(x, SameTypeAndValue(feet(6.0))); +} + +TEST(Quantity, ShorthandDivisionAssignmentWorksForComplexRepAndScalar) { + auto test = meters(std::complex{25.0f, 12.5f}); + test /= std::complex{3.0f, 4.0f}; + EXPECT_THAT(test, SameTypeAndValue(meters(std::complex{5.0f, -2.5f}))); +} + TEST(Quantity, CanDivideArbitraryQuantities) { constexpr auto d = feet(6.); constexpr auto t = hours(3.);