Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permit arithmetic operations with more types #229

Merged
merged 1 commit into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions au/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ cc_library(
":apply_magnitude",
":conversion_policy",
":operators",
":rep",
":unit_of_measure",
":zero",
],
Expand Down Expand Up @@ -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([
Expand Down
9 changes: 5 additions & 4 deletions au/quantity.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -293,21 +294,21 @@ class Quantity {
}

// Scalar multiplication.
template <typename T, typename = std::enable_if_t<std::is_arithmetic<T>::value>>
template <typename T, typename = std::enable_if_t<IsProductValidRep<RepT, T>::value>>
friend constexpr auto operator*(Quantity a, T s) {
return make_quantity<UnitT>(a.value_ * s);
}
template <typename T, typename = std::enable_if_t<std::is_arithmetic<T>::value>>
template <typename T, typename = std::enable_if_t<IsProductValidRep<T, RepT>::value>>
friend constexpr auto operator*(T s, Quantity a) {
return make_quantity<UnitT>(s * a.value_);
}

// Scalar division.
template <typename T, typename = std::enable_if_t<std::is_arithmetic<T>::value>>
template <typename T, typename = std::enable_if_t<IsQuotientValidRep<RepT, T>::value>>
friend constexpr auto operator/(Quantity a, T s) {
return make_quantity<UnitT>(a.value_ / s);
}
template <typename T, typename = std::enable_if_t<std::is_arithmetic<T>::value>>
template <typename T, typename = std::enable_if_t<IsQuotientValidRep<T, RepT>::value>>
friend constexpr auto operator/(T s, Quantity a) {
warn_if_integer_division<T>();
return make_quantity<decltype(pow<-1>(unit))>(s / a.value_);
Expand Down
32 changes: 32 additions & 0 deletions au/quantity_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@

#include "au/quantity.hh"

#include <complex>

#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 {
Expand Down Expand Up @@ -425,6 +428,35 @@ TEST(Quantity, ScalarMultiplicationWorks) {
EXPECT_EQ(feet(9), d * 3);
}

TEST(Quantity, SupportsMultiplicationForComplexRep) {
constexpr auto a = (miles / hour)(std::complex<double>{1.0, -2.0});
constexpr auto b = hours(std::complex<double>{-3.0, 4.0});
EXPECT_THAT(a * b, SameTypeAndValue(miles(std::complex<double>{5.0, 10.0})));
}

TEST(Quantity, SupportsMultiplicationOfRealQuantityByComplexCoefficient) {
constexpr auto a = miles(10.0);
constexpr auto b = std::complex<double>{-3.0, 4.0};
EXPECT_THAT(a * b, SameTypeAndValue(miles(std::complex<double>{-30.0, 40.0})));
EXPECT_THAT(b * a, SameTypeAndValue(miles(std::complex<double>{-30.0, 40.0})));
}

TEST(Quantity, SupportsDivisionOfRealQuantityByComplexCoefficient) {
constexpr auto a = miles(100.0);
constexpr auto b = std::complex<double>{-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<double>{-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.);
Expand Down
148 changes: 148 additions & 0 deletions au/rep.hh
Original file line number Diff line number Diff line change
@@ -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 <type_traits>

#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 <typename T>
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 <typename T, typename U>
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 <typename T, typename U>
struct IsQuotientValidRep;

////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation details below.
////////////////////////////////////////////////////////////////////////////////////////////////////

// Forward declarations for main Au container types.
template <typename U, typename R>
class Quantity;
template <typename U, typename R>
class QuantityPoint;
template <typename T>
struct CorrespondingQuantity;

namespace detail {
template <typename T>
struct IsAuType : std::false_type {};

template <typename U, typename R>
struct IsAuType<::au::Quantity<U, R>> : std::true_type {};

template <typename U, typename R>
struct IsAuType<::au::QuantityPoint<U, R>> : std::true_type {};

template <typename T>
using CorrespondingUnit = typename CorrespondingQuantity<T>::Unit;

template <typename T>
using CorrespondingRep = typename CorrespondingQuantity<T>::Rep;

template <typename T>
struct HasCorrespondingQuantity
: stdx::conjunction<stdx::experimental::is_detected<CorrespondingUnit, T>,
stdx::experimental::is_detected<CorrespondingRep, T>> {};

template <typename T>
using LooksLikeAuOrOtherQuantity = stdx::disjunction<IsAuType<T>, HasCorrespondingQuantity<T>>;

// 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 <template <class...> class Op, typename... Ts>
struct ResultIfNoneAreQuantity;
template <template <class...> class Op, typename... Ts>
using ResultIfNoneAreQuantityT = typename ResultIfNoneAreQuantity<Op, Ts...>::type;

// Default implementation where we know that none are quantities.
template <bool AreAnyQuantity, template <class...> class Op, typename... Ts>
struct ResultIfNoneAreQuantityImpl : stdx::type_identity<Op<Ts...>> {};

// Implementation if any of the types are quantities.
template <template <class...> class Op, typename... Ts>
struct ResultIfNoneAreQuantityImpl<true, Op, Ts...> : stdx::type_identity<void> {};

// The main implementation.
template <template <class...> class Op, typename... Ts>
struct ResultIfNoneAreQuantity
: ResultIfNoneAreQuantityImpl<stdx::disjunction<LooksLikeAuOrOtherQuantity<Ts>...>::value,
Op,
Ts...> {};

// The `std::is_empty` is a good way to catch all of the various unit and other monovalue types in
// our library, which have little else in common. It's also just intrinsically true that it
// wouldn't make much sense to use an empty type as a rep.
template <typename T>
struct IsKnownInvalidRep
: stdx::disjunction<std::is_empty<T>, LooksLikeAuOrOtherQuantity<T>, std::is_same<void, T>> {};

// The type of the product of two types.
template <typename T, typename U>
using ProductType = decltype(std::declval<T>() * std::declval<U>());

template <typename T, typename U>
using ProductTypeOrVoid = stdx::experimental::detected_or_t<void, ProductType, T, U>;

// The type of the quotient of two types.
template <typename T, typename U>
using QuotientType = decltype(std::declval<T>() / std::declval<U>());

template <typename T, typename U>
using QuotientTypeOrVoid = stdx::experimental::detected_or_t<void, QuotientType, T, U>;
} // namespace detail

// Implementation for `IsValidRep`.
//
// For now, we'll accept anything that isn't explicitly known to be invalid. We may tighten this up
// later, but this seems like a reasonable starting point.
template <typename T>
struct IsValidRep : stdx::negation<detail::IsKnownInvalidRep<T>> {};

template <typename T, typename U>
struct IsProductValidRep
: IsValidRep<detail::ResultIfNoneAreQuantityT<detail::ProductTypeOrVoid, T, U>> {};

template <typename T, typename U>
struct IsQuotientValidRep
: IsValidRep<detail::ResultIfNoneAreQuantityT<detail::QuotientTypeOrVoid, T, U>> {};

} // namespace au
Loading
Loading