diff --git a/au/BUILD.bazel b/au/BUILD.bazel index 7e945707..5843a97b 100644 --- a/au/BUILD.bazel +++ b/au/BUILD.bazel @@ -395,6 +395,7 @@ cc_library( ":apply_magnitude", ":conversion_policy", ":operators", + ":rep", ":unit_of_measure", ":zero", ], @@ -438,6 +439,30 @@ cc_test( ], ) +cc_library( + name = "rep", + hdrs = ["rep.hh"], + deps = [":stdx"], +) + +cc_test( + name = "rep_test", + size = "small", + srcs = ["rep_test.cc"], + deps = [ + ":chrono_interop", + ":constant", + ":magnitude", + ":prefix", + ":quantity", + ":quantity_point", + ":rep", + ":unit_symbol", + ":units", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "stdx", srcs = glob([ diff --git a/au/quantity.hh b/au/quantity.hh index fb3752b9..b567d43a 100644 --- a/au/quantity.hh +++ b/au/quantity.hh @@ -19,6 +19,7 @@ #include "au/apply_magnitude.hh" #include "au/conversion_policy.hh" #include "au/operators.hh" +#include "au/rep.hh" #include "au/stdx/functional.hh" #include "au/unit_of_measure.hh" #include "au/zero.hh" @@ -293,21 +294,21 @@ class Quantity { } // Scalar multiplication. - template ::value>> + template ::value>> friend constexpr auto operator*(Quantity a, T s) { return make_quantity(a.value_ * s); } - template ::value>> + template ::value>> friend constexpr auto operator*(T s, Quantity a) { return make_quantity(s * a.value_); } // Scalar division. - template ::value>> + template ::value>> friend constexpr auto operator/(Quantity a, T s) { return make_quantity(a.value_ / s); } - template ::value>> + template ::value>> friend constexpr auto operator/(T s, Quantity a) { warn_if_integer_division(); return make_quantity(unit))>(s / a.value_); diff --git a/au/quantity_test.cc b/au/quantity_test.cc index 8e582333..733fa619 100644 --- a/au/quantity_test.cc +++ b/au/quantity_test.cc @@ -14,11 +14,14 @@ #include "au/quantity.hh" +#include + #include "au/prefix.hh" #include "au/testing.hh" #include "au/utility/type_traits.hh" #include "gtest/gtest.h" +using ::testing::DoubleEq; using ::testing::StaticAssertTypeEq; namespace au { @@ -425,6 +428,35 @@ TEST(Quantity, ScalarMultiplicationWorks) { EXPECT_EQ(feet(9), d * 3); } +TEST(Quantity, SupportsMultiplicationForComplexRep) { + constexpr auto a = (miles / hour)(std::complex{1.0, -2.0}); + constexpr auto b = hours(std::complex{-3.0, 4.0}); + EXPECT_THAT(a * b, SameTypeAndValue(miles(std::complex{5.0, 10.0}))); +} + +TEST(Quantity, SupportsMultiplicationOfRealQuantityByComplexCoefficient) { + constexpr auto a = miles(10.0); + constexpr auto b = std::complex{-3.0, 4.0}; + EXPECT_THAT(a * b, SameTypeAndValue(miles(std::complex{-30.0, 40.0}))); + EXPECT_THAT(b * a, SameTypeAndValue(miles(std::complex{-30.0, 40.0}))); +} + +TEST(Quantity, SupportsDivisionOfRealQuantityByComplexCoefficient) { + constexpr auto a = miles(100.0); + constexpr auto b = std::complex{-3.0, 4.0}; + const auto quotient = (a / b).in(miles); + EXPECT_THAT(quotient.real(), DoubleEq(-12.0)); + EXPECT_THAT(quotient.imag(), DoubleEq(-16.0)); +} + +TEST(Quantity, SupportsDivisionOfRealQuantityIntoComplexCoefficient) { + constexpr auto a = std::complex{-30.0, 40.0}; + constexpr auto b = miles(10.0); + const auto quotient = (a / b).in(inverse(miles)); + EXPECT_THAT(quotient.real(), DoubleEq(-3.0)); + EXPECT_THAT(quotient.imag(), DoubleEq(4.0)); +} + TEST(Quantity, CanDivideArbitraryQuantities) { constexpr auto d = feet(6.); constexpr auto t = hours(3.); diff --git a/au/rep.hh b/au/rep.hh new file mode 100644 index 00000000..562e8226 --- /dev/null +++ b/au/rep.hh @@ -0,0 +1,148 @@ +// Copyright 2024 Aurora Operations, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "au/stdx/experimental/is_detected.hh" +#include "au/stdx/type_traits.hh" + +namespace au { + +// +// A type trait that determines if a type is a valid representation type for `Quantity` or +// `QuantityPoint`. +// +template +struct IsValidRep; + +// +// A type trait to indicate whether the product of two types is a valid rep. +// +// Will validly return `false` if the product does not exist. +// +template +struct IsProductValidRep; + +// +// A type trait to indicate whether the quotient of two types is a valid rep. +// +// Will validly return `false` if the quotient does not exist. +// +template +struct IsQuotientValidRep; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Implementation details below. +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Forward declarations for main Au container types. +template +class Quantity; +template +class QuantityPoint; +template +struct CorrespondingQuantity; + +namespace detail { +template +struct IsAuType : std::false_type {}; + +template +struct IsAuType<::au::Quantity> : std::true_type {}; + +template +struct IsAuType<::au::QuantityPoint> : std::true_type {}; + +template +using CorrespondingUnit = typename CorrespondingQuantity::Unit; + +template +using CorrespondingRep = typename CorrespondingQuantity::Rep; + +template +struct HasCorrespondingQuantity + : stdx::conjunction, + stdx::experimental::is_detected> {}; + +template +using LooksLikeAuOrOtherQuantity = stdx::disjunction, HasCorrespondingQuantity>; + +// We need a way to form an "operation on non-quantity types only". That is: it's some operation, +// but _if either input is a quantity_, then we _don't even form the type_. +// +// The reason this very specific machinery lives in `rep.hh` is because when we're dealing with +// operations on "types that might be a rep", we know we can exclude quantity types right away. +// (Note that we're using the term "quantity" in an expansive sense, which includes not just +// `au::Quantity`, but also `au::QuantityPoint`, and "quantity-like" types from other libraries +// (which we consider as "anything that has a `CorrespondingQuantity`". +template