feat: full-on dimensional analysis between related quantity kinds

This commit is contained in:
Johel Ernesto Guerrero Peña
2021-02-16 18:40:03 -04:00
committed by Mateusz Pusz
parent dad9b4f166
commit 017183653d
11 changed files with 295 additions and 49 deletions

View File

@@ -49,6 +49,7 @@ set(unitsSphinxDocs
"${CMAKE_CURRENT_SOURCE_DIR}/design/directories.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/design/downcasting.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/design/quantity.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/design/quantity_kind.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/examples.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/examples/avg_speed.rst"

View File

@@ -0,0 +1,52 @@
.. namespace:: units
quantity_kind
=============
The ``quantity_kind`` class template provides a similar interface to ``quantity``.
Kinds
-----
The first template parameter of ``quantity_kind`` is a ``Kind``.
``Kind``s, by themselves:
* Wrap a ``Dimension``, and
* opt into the downcasting facility.
If a dimensional analysis operator on a quantity of kind *A*
can result on a quantity of kind *B*,
*A* and *B* are *related*.
The library provides two ``Kind`` bases, ``kind`` and ``derived_kind``.
``kind`` is an entry point, and there's only one per set of related kinds.
``derived_kind`` is used to explicitly name other related kinds.
Unnamed, they look like ``detail::_kind_base<the-entry-kind, some_dimension>``.
Unnamed kinds, like unnamed units and dimensions,
allows (intermediate) results in formulas without having to name them.
Quantity kinds
--------------
``quantity_kind`` wraps a ``quantity`` and layers over its dimensional analysis.
While all properties of the ``quantity`` apply transparently,
results are always ``quantity_kind``s related to the kind(s) of the argument(s).
One of the arguments to the dimensional analysis operators can also be ``Quantity``.
The way back to ``quantity`` is through the ``.common()`` observer.
Intra-kind operations
---------------------
``quantity_kind`` takes care of operators between related quantity kinds.
Intra-kind operations can be opted into as follows::
struct width : kind<width, dim_length> {};
struct height : kind<height, dim_length> {};
size2d operator+(QuantityKindOf<width> auto, QuantityKindOf<height> auto);

View File

@@ -67,23 +67,40 @@ dimension, than we will end up with just a dimensionless quantity:
Quantity Kinds
++++++++++++++
Quantity kinds behave the same as quantities for addition and subtraction.
The same behavior is also provided for multiplication with, and division by
a :term:`scalable number`.
Multiplication and division with a quantity (but not a quantity kind) is allowed.
The result is a quantity kind with the appropriate dimension
and related to the original quantity kind:
Quantity kinds behave the same as quantities for all operations,
except that the quantity types in the operators' declarations
are quantity kind types instead.
Additionally, for the dimensional analysis operators,
you can use a quantity argument instead of a quantity kind.
.. code-block::
:emphasize-lines: 5-6
:emphasize-lines: 8-9
struct height : kind<height, dim_length> {};
struct rate_of_climb : derived_kind<rate_of_climb, height, dim_speed> {};
struct height_kind : kind<height_kind, dim_length> {};
struct rate_of_climb_kind : derived_kind<rate_of_climb_kind, height_kind, dim_speed> {};
quantity_kind h(height{}, 100 * m);
quantity_point_kind rate = h / (25 * s);
// quantity_point_kind<rate_of_climb, si::metre_per_second, int>(4 * m / s)
template <Unit U, QuantityValue Rep = double> using height = quantity_kind<height_kind, U, Rep>;
template <Unit U, QuantityValue Rep = double> using rate_of_climb = quantity_kind<rate_of_climb_kind, U, Rep>;
height h{100 * m};
rate_of_climb rate = h / (25 * s);
// quantity_kind<rate_of_climb_kind, si::metre_per_second, int>(4 * m / s)
.. code-block::
:emphasize-lines: 8-12
struct width_kind : kind<width_kind, dim_length> {};
struct horizontal_area_kind : derived_kind<horizontal_area_kind, width_kind, dim_area> {};
template <Unit U, QuantityValue Rep = double> using width = quantity_kind<width_kind, U, Rep>;
template <Unit U, QuantityValue Rep = double> using horizontal_area = quantity_kind<horizontal_area_kind, U, Rep>;
width w{5 * m};
horizontal_area area1 = w * w;
// quantity_kind<horizontal_area_kind, si::metre_per_second, int>(25 * m * m)
width w2 = area1 / w; // quantity_kind<width_kind, si::metre, int>(5 * m)
auto q1 = w / w; // Dimensionless quantity kinds related to width
auto q2 = w / (5 * m); // with .common() equal to quantity{1}
Quantity Points
+++++++++++++++

View File

@@ -41,7 +41,7 @@ the value to the `quantity_kind` class template constructor::
Differences To Quantity
-----------------------
Unlike `quantity`, the library provides:
The library provides:
- no kinds, such as ``radius`` or ``width``, therefore
@@ -49,7 +49,7 @@ Unlike `quantity`, the library provides:
* no kind-specific concepts, such as ``Radius``,
(there's the generic `QuantityKind` and kind-specifiable `QuantityKindOf`),
- a slightly different set of operations on quantity kinds
- a slightly larger set of operations on quantity kinds
(see the :ref:`framework/dimensions:Quantity Kinds` chapter).

View File

@@ -100,8 +100,8 @@ using namespace si::international::literals;
using namespace si::unit_constants;
template<QuantityKind QK1, QuantityKind QK2>
constexpr Quantity auto operator/(const QK1& lhs, const QK2& rhs)
requires requires { lhs.common() / rhs.common(); } {
constexpr Dimensionless auto operator/(const QK1& lhs, const QK2& rhs)
requires (!units::QuantityKindRelatedTo<QK1, QK2>) && requires { lhs.common() / rhs.common(); } {
return lhs.common() / rhs.common();
}
@@ -258,7 +258,7 @@ struct flight_point {
constexpr altitude terrain_level_alt(const task& t, distance dist)
{
const height alt_diff = t.finish.alt - t.start.alt;
return t.start.alt + alt_diff * (dist / t.dist);
return t.start.alt + alt_diff * (dist / t.dist).common();
}
constexpr height agl(altitude glider_alt, altitude terrain_level)
@@ -283,7 +283,7 @@ void print(std::string_view phase_name, const flight_point& point, const flight_
flight_point tow(const flight_point& point, const aircraft_tow& at)
{
const duration d = at.height_agl / at.performance;
const duration d = (at.height_agl / at.performance).common();
const flight_point new_point{point.dur + d, point.dist, point.alt + at.height_agl};
print("Tow", point, new_point);
@@ -295,7 +295,7 @@ flight_point circle(const flight_point& point, const glider& g, const weather& w
const height h_agl = agl(point.alt, terrain_level_alt(t, point.dist));
const height circle_height = std::min(w.cloud_base - h_agl, height_to_gain);
const rate_of_climb circling_rate = w.thermal_strength + g.polar[0].climb;
const duration d = circle_height / circling_rate;
const duration d = (circle_height / circling_rate).common();
const flight_point new_point{point.dur + d, point.dist, point.alt + circle_height};
height_to_gain -= circle_height;

View File

@@ -246,6 +246,16 @@ public:
return *this;
}
template<typename Rep2>
constexpr quantity& operator%=(const dimensionless<units::one, Rep2>& rhs)
requires (!floating_point_<rep>) && (!floating_point_<Rep2>) &&
requires(rep a, const Rep2 b) { { a %= b } -> std::same_as<rep&>; }
{
gsl_ExpectsAudit(rhs.count() != quantity_values<Rep2>::zero());
value_ %= rhs.count();
return *this;
}
constexpr quantity& operator%=(const quantity& q)
requires (!floating_point_<rep>) &&
requires(rep a, rep b) { { a %= b } -> std::same_as<rep&>; }

View File

@@ -53,6 +53,15 @@ inline constexpr auto& downcasted_kind = downcasted_kind_fn<typename QK::kind_ty
} // namespace detail
/**
* @brief A concept matching two related quantity kinds
*
* Satisfied by quantity kinds having equivalent base kinds.
*/
template<typename QK1, typename QK2>
concept QuantityKindRelatedTo = QuantityKind<QK1> && QuantityKind<QK2> &&
equivalent<typename QK1::kind_type::base_kind, typename QK2::kind_type::base_kind>;
/**
* @brief A quantity kind
*
@@ -180,6 +189,13 @@ public:
q_ *= rhs;
return *this;
}
template<typename Rep2>
constexpr quantity_kind& operator*=(const quantity_kind<downcast_kind<K, dim_one>, units::one, Rep2>& rhs)
requires requires(quantity_type q) { q *= rhs.common(); }
{
q_ *= rhs.common();
return *this;
}
template<typename Rep2>
constexpr quantity_kind& operator/=(const Rep2& rhs)
@@ -189,15 +205,29 @@ public:
q_ /= rhs;
return *this;
}
template<typename Rep2>
constexpr quantity_kind& operator/=(const quantity_kind<downcast_kind<K, dim_one>, units::one, Rep2>& rhs)
requires requires(quantity_type q) { q /= rhs.common(); }
{
q_ /= rhs.common();
return *this;
}
template<typename Rep2>
constexpr quantity_kind& operator%=(const Rep2& rhs)
requires (!Quantity<Rep2>) && requires(quantity_type q, const Rep2 r) { q %= r; }
requires (!Quantity<Rep2> || Dimensionless<Rep2>) && requires(quantity_type q, const Rep2 r) { q %= r; }
{
gsl_ExpectsAudit(rhs != quantity_values<Rep2>::zero());
q_ %= rhs;
return *this;
}
template<typename Rep2>
constexpr quantity_kind& operator%=(const quantity_kind<downcast_kind<K, dim_one>, units::one, Rep2>& rhs)
requires requires(quantity_type q) { q %= rhs.common(); }
{
q_ %= rhs.common();
return *this;
}
constexpr quantity_kind& operator%=(const quantity_kind& qk)
requires requires(quantity_type q) { q %= qk.common(); }
@@ -256,9 +286,6 @@ public:
[[nodiscard]] friend constexpr bool operator==(const quantity_kind&, const quantity_kind&) = default;
};
template<Kind K, QuantityValue V>
explicit(false) quantity_kind(K, V) -> quantity_kind<K, one, V>;
template<QuantityKind QK1, QuantityKindEquivalentTo<QK1> QK2>
[[nodiscard]] constexpr QuantityKind auto operator+(const QK1& lhs, const QK2& rhs)
requires requires { lhs.common() + rhs.common(); }
@@ -287,6 +314,13 @@ template<Quantity Q, QuantityKind QK>
return detail::downcasted_kind<QK>(lhs * rhs.common());
}
template<QuantityKind QK1, QuantityKindRelatedTo<QK1> QK2>
[[nodiscard]] constexpr QuantityKind auto operator*(const QK1& lhs, const QK2& rhs)
requires requires { lhs.common() * rhs.common(); }
{
return detail::downcasted_kind<QK1>(lhs.common() * rhs.common());
}
template<QuantityKind QK, Quantity Q>
[[nodiscard]] constexpr QuantityKind auto operator/(const QK& lhs, const Q& rhs)
requires requires { lhs.common() / rhs; }
@@ -303,6 +337,13 @@ template<Quantity Q, QuantityKind QK>
return detail::downcasted_kind<QK>(lhs / rhs.common());
}
template<QuantityKind QK1, QuantityKindRelatedTo<QK1> QK2>
[[nodiscard]] constexpr QuantityKind auto operator/(const QK1& lhs, const QK2& rhs)
requires requires { lhs.common() / rhs.common(); }
{
return detail::downcasted_kind<QK1>(lhs.common() / rhs.common());
}
template<QuantityKind QK, Dimensionless D>
[[nodiscard]] constexpr QuantityKind auto operator%(const QK& lhs, const D& rhs)
requires requires { lhs.common() % rhs; }

View File

@@ -182,9 +182,6 @@ public:
};
template<PointKind PK, QuantityValue V>
explicit(false) quantity_point_kind(PK, V) -> quantity_point_kind<PK, one, V>;
template<QuantityKind QK>
quantity_point_kind(QK) ->
quantity_point_kind<downcast_point_kind<typename QK::kind_type>, typename QK::unit, typename QK::rep>;

View File

@@ -309,6 +309,7 @@ static_assert(construct_from_only<width<cgs::centimetre, double>>(1.0 * cm).comm
static_assert(construct_and_convert_from<width<metre, int>>(width<metre, int>(1 * m)).common() == 1 * m);
static_assert(construct_and_convert_from<width<centimetre, int>>(width<cgs::centimetre, int>(1 * cgs_cm)).common() == 1 * cm);
static_assert(construct_and_convert_from<width<fps::foot, double>>(width<cgs::centimetre, int>(1 * cgs_cm)).common() == 1 * cm);
// clang-format on
static_assert(construct_and_convert_from<width<metre, double>>(width<metre, int>(1 * m)).common() == 1 * m);
static_assert(!constructible_or_convertible_from<width<metre, int>>(width<metre, double>(1.0 * m)));
@@ -322,7 +323,6 @@ static_assert(construct_and_convert_from<width<kilometre, double>>(width<metre,
static_assert(!constructible_or_convertible_from<width<metre, int>>(height<metre, int>(1 * m)));
static_assert(!constructible_or_convertible_from<apples<one, int>>(width<metre, int>(1 * m) / m));
static_assert(!constructible_or_convertible_from<apples<one, int>>(oranges<one, int>(1)));
// clang-format on
//////////////////////////////////
@@ -380,23 +380,42 @@ static_assert([]() {
assert(&(w -= w) == &w && w.common() == 0 * m);
w = width<metre, int>(3 * m);
assert(&(w *= 3) == &w && w.common() == 9 * m);
assert(&(w *= quantity(1)) == &w && w.common() == 9 * m);
assert(&(w *= (w / w)) == &w && w.common() == 9 * m);
assert(&(w /= 2) == &w && w.common() == 4 * m);
assert(&(w /= quantity(1)) == &w && w.common() == 4 * m);
assert(&(w /= (w / w)) == &w && w.common() == 4 * m);
assert(&(w %= 3) == &w && w.common() == 1 * m);
assert(&(w %= quantity(3)) == &w && w.common() == 1 * m);
assert(&(w %= 3 * (w / w)) == &w && w.common() == 1 * m);
assert(&(w %= w) == &w && w.common() == 0 * m);
w = width<metre, int>(3 * m);
assert(&(w *= 3.9) == &w && w.common() == 11 * m);
assert(&(w *= quantity(1.0)) == &w && w.common() == 11 * m);
assert(&(w *= 1.0 * (w / w)) == &w && w.common() == 11 * m);
assert(&(w /= 3.9) == &w && w.common() == 2 * m);
assert(&(w /= quantity(1.0)) == &w && w.common() == 2 * m);
assert(&(w /= 1.0 * (w / w)) == &w && w.common() == 2 * m);
return true;
}());
#endif
static_assert((std::uint8_t(255) * m %= 256) == (width<metre, std::uint8_t>(255 * m) %= 256).common());
static_assert((std::uint8_t(255) * m %= quantity(256)) ==
(width<metre, std::uint8_t>(255 * m) %= quantity(256)).common());
// static_assert((std::uint8_t(255) * m %= 256 * m) ==
// (width<metre, std::uint8_t>(255 * m) %=
// quantity_kind<downcast_kind<width_kind, dim_one>, one, std::uint8_t>(256)).common()); // UB
// static_assert((std::uint8_t(255) * m %= 256 * m) !=
// (width<metre, std::uint8_t>(255 * m) %= width<metre, std::uint8_t>(256 * m)).common()); // UB
// (width<metre, std::uint8_t>(255 * m) %= width<metre, std::uint8_t>(256 * m)).common()); // UB
static_assert((std::uint8_t(255) * m %= 257) == (width<metre, std::uint8_t>(255 * m) %= 257).common());
// TODO: Fix
static_assert((std::uint8_t(255) * m %= quantity(257)) ==
(width<metre, std::uint8_t>(255 * m) %= quantity(257)).common());
static_assert((std::uint8_t(255) * m %= 257 * m) ==
(width<metre, std::uint8_t>(255 * m) %= width<metre, std::uint8_t>(257 * m)).common());
(width<metre, std::uint8_t>(255 * m) %=
quantity_kind<downcast_kind<width_kind, dim_one>, one, std::uint8_t>(257)).common());
static_assert((std::uint8_t(255) * m %= 257 * m) ==
(width<metre, std::uint8_t>(255 * m) %= width<metre, std::uint8_t>(257 * m)).common());
static_assert(same((-width<metre, short>(short{1} * m)).common(), int{-1} * m));
@@ -409,11 +428,20 @@ concept invalid_compound_assignments_ = requires(quantity_kind<K, U, int> w, Qx
requires !requires { w %= q; };
};
template<typename Width>
concept invalid_compound_assignments = requires(quantity_kind<Width, metre, int> w) {
concept invalid_compound_assignments = requires(quantity_kind<Width, metre, int> w, height<metre, int> h) {
requires !requires { w += 1; };
requires !requires { w -= 1; };
requires !requires { w *= 1 * km / m; };
requires !requires { w /= 1 * km / m; };
requires !requires { w %= 1 * km / m; };
requires !requires { w *= quantity_kind<downcast_kind<Width, dim_one>, scaled_unit<ratio{1, 1, 3}, one>, int>{1}; };
requires !requires { w /= quantity_kind<downcast_kind<Width, dim_one>, scaled_unit<ratio{1, 1, 3}, one>, int>{1}; };
requires !requires { w %= quantity_kind<downcast_kind<Width, dim_one>, scaled_unit<ratio{1, 1, 3}, one>, int>{1}; };
requires !requires { w %= 1.0; };
requires !requires { w %= w * 1.0; };
requires !requires { w %= quantity(1.0); };
requires !requires { w %= 1.0 * (w / w); };
requires !requires { w %= 1.0 * w; };
requires !requires { w %= h / h; };
requires invalid_compound_assignments_<Width, metre, length<metre, int>>;
requires invalid_compound_assignments_<Width, metre, height<metre, int>>;
requires invalid_compound_assignments_<Width, metre, horizontal_area<square_metre, int>>;
@@ -477,6 +505,26 @@ static_assert(same(2 * width<metre, int>(3 * m), width<metre, int>(6 * m)));
static_assert(same(2 * width<metre, double>(3. * m), width<metre, double>(6. * m)));
static_assert(same(2. * width<metre, int>(3 * m), width<metre, double>(6. * m)));
static_assert(same(width<metre, int>(2 * m) * quantity(3), width<metre, int>(6 * m)));
static_assert(same(width<metre, int>(2 * m) * quantity(3.), width<metre, double>(6. * m)));
static_assert(same(width<metre, double>(2. * m) * quantity(3), width<metre, double>(6. * m)));
static_assert(same(quantity(2) * width<metre, int>(3 * m), width<metre, int>(6 * m)));
static_assert(same(quantity(2) * width<metre, double>(3. * m), width<metre, double>(6. * m)));
static_assert(same(quantity(2.) * width<metre, int>(3 * m), width<metre, double>(6. * m)));
static_assert(same(width<metre, int>(2 * m) * quantity_kind<downcast_kind<width_kind, dim_one>, one, int>(3),
width<metre, int>(6 * m)));
static_assert(same(width<metre, int>(2 * m) * quantity_kind<downcast_kind<width_kind, dim_one>, one, double>(3.),
width<metre, double>(6. * m)));
static_assert(same(width<metre, double>(2. * m) * quantity_kind<downcast_kind<width_kind, dim_one>, one, int>(3),
width<metre, double>(6. * m)));
static_assert(same(quantity_kind<downcast_kind<width_kind, dim_one>, one, int>(2) * width<metre, int>(3 * m),
width<metre, int>(6 * m)));
static_assert(same(quantity_kind<downcast_kind<width_kind, dim_one>, one, int>(2) * width<metre, double>(3. * m),
width<metre, double>(6. * m)));
static_assert(same(quantity_kind<downcast_kind<width_kind, dim_one>, one, double>(2.) * width<metre, int>(3 * m),
width<metre, double>(6. * m)));
static_assert(same(height<metre, int>(2 * m) * (3 * Hz), rate_of_climb<metre_per_second, int>(6 * m / s)));
static_assert(same(height<metre, int>(2 * m) * (3. * Hz), rate_of_climb<metre_per_second, double>(6. * m / s)));
static_assert(same(height<metre, double>(2. * m) * (3 * Hz), rate_of_climb<metre_per_second, double>(6. * m / s)));
@@ -484,13 +532,65 @@ static_assert(same((2 * Hz) * height<metre, int>(3 * m), rate_of_climb<metre_per
static_assert(same((2 * Hz) * height<metre, double>(3. * m), rate_of_climb<metre_per_second, double>(6. * m / s)));
static_assert(same((2. * Hz) * height<metre, int>(3 * m), rate_of_climb<metre_per_second, double>(6. * m / s)));
static_assert(same(quantity_kind<time_kind, second, int>(2 * s) * (3 * Hz),
quantity_kind<downcast_kind<time_kind, dim_one>, one, int>(6)));
static_assert(same((3 * Hz) * quantity_kind<time_kind, second, int>(2 * s),
quantity_kind<downcast_kind<time_kind, dim_one>, one, int>(6)));
static_assert(same(apples<one, int>(2) * quantity(2), apples<one, int>(4)));
static_assert(same(quantity(2) * apples<one, int>(2), apples<one, int>(4)));
// clang-format off
static_assert(same(width<metre, int>(2 * m) * width<metre, int>(2 * m), horizontal_area<square_metre, int>(4 * m * m)));
static_assert(same(width<metre, int>(2 * m) * width<metre, double>(2 * m), horizontal_area<square_metre, double>(4 * m * m)));
static_assert(same(width<metre, double>(2 * m) * width<metre, int>(2 * m), horizontal_area<square_metre, double>(4 * m * m)));
// clang-format on
static_assert(same(apples<one, int>(2) * apples<one, int>(2), apples<one, int>(4)));
static_assert(same(apples<one, int>(2) * (2 / apples<one, int>(1)), apples<one, int>(4)));
static_assert(same(width<kilometre>(2 * m) * width<millimetre>(2 * m), horizontal_area<square_metre>(4 * m * m)));
static_assert(same(width<metre>(2 * m) * (1 / width<metre>(2 * m)),
quantity_kind<downcast_kind<width_kind, dim_one>, one>(1)));
static_assert(same(width<metre, int>(2 * m) / 3, width<metre, int>(0 * m)));
static_assert(same(width<metre, int>(2 * m) / 3., width<metre, double>(2 / 3. * m)));
static_assert(same(width<metre, double>(2. * m) / 3, width<metre, double>(2. / 3 * m)));
static_assert(same((2 / width<metre, int>(3 * m)).common(), 2 / 3 / m));
static_assert(same((2 / width<metre, double>(3. * m)).common(), 2 / 3. / m));
static_assert(same((2. / width<metre, int>(3 * m)).common(), 2. / 3 / m));
static_assert(same(width<metre, int>(2 * m) / quantity(3), width<metre, int>(0 * m)));
static_assert(same(width<metre, int>(2 * m) / quantity(3.), width<metre, double>(2 / 3. * m)));
static_assert(same(width<metre, double>(2. * m) / quantity(3), width<metre, double>(2. / 3 * m)));
static_assert(same(width<metre, int>(2 * m) / quantity_kind<downcast_kind<width_kind, dim_one>, one, int>(3),
width<metre, int>(0 * m)));
static_assert(same(width<metre, int>(2 * m) / quantity_kind<downcast_kind<width_kind, dim_one>, one, double>(3.),
width<metre, double>(2 / 3. * m)));
static_assert(same(width<metre, double>(2. * m) / quantity_kind<downcast_kind<width_kind, dim_one>, one, double>(3),
width<metre, double>(2. / 3 * m)));
static_assert(same(2 / quantity_kind<time_kind, second, int>(3 * s),
quantity_kind<downcast_kind<time_kind, dim_frequency>, hertz, int>(2 / 3 / s)));
static_assert(same(2 / quantity_kind<time_kind, second, double>(3. * s),
quantity_kind<downcast_kind<time_kind, dim_frequency>, hertz, double>(2 / 3. / s)));
static_assert(same(2. / quantity_kind<time_kind, second, int>(3 * s),
quantity_kind<downcast_kind<time_kind, dim_frequency>, hertz, double>(2 / 3. / s)));
static_assert(same(quantity(2) / quantity_kind<time_kind, second, int>(3 * s),
quantity_kind<downcast_kind<time_kind, dim_frequency>, hertz, int>(2 / 3 / s)));
static_assert(same(quantity(2) / quantity_kind<time_kind, second, double>(3. * s),
quantity_kind<downcast_kind<time_kind, dim_frequency>, hertz, double>(2 / 3. / s)));
static_assert(same(quantity(2.) / quantity_kind<time_kind, second, int>(3 * s),
quantity_kind<downcast_kind<time_kind, dim_frequency>, hertz, double>(2 / 3. / s)));
static_assert(
same(quantity_kind<downcast_kind<time_kind, dim_one>, one, int>(2) / quantity_kind<time_kind, second, int>(3 * s),
quantity_kind<downcast_kind<time_kind, dim_frequency>, hertz, int>(2 / 3 / s)));
static_assert(
same(quantity_kind<downcast_kind<time_kind, dim_one>, one, int>(2) / quantity_kind<time_kind, second, double>(3. * s),
quantity_kind<downcast_kind<time_kind, dim_frequency>, hertz, double>(2 / 3. / s)));
static_assert(
same(quantity_kind<downcast_kind<time_kind, dim_one>, one, double>(2.) / quantity_kind<time_kind, second, int>(3 * s),
quantity_kind<downcast_kind<time_kind, dim_frequency>, hertz, double>(2 / 3. / s)));
static_assert(same(height<metre, int>(2 * m) / (3 * s), rate_of_climb<metre_per_second, int>(0 * m / s)));
static_assert(same(height<metre, int>(2 * m) / (3. * s), rate_of_climb<metre_per_second, double>(2 / 3. * m / s)));
@@ -501,11 +601,31 @@ static_assert(same(dimensionless<percent, int>(2) * width<metre, int>(3 * m), wi
static_assert(same(width<metre, int>(2 * m) / dimensionless<percent, double>(3), width<hectometre, double>(2. / 3 * hm)));
static_assert(same(width<metre, int>(2 * m) % dimensionless<percent, int>(3), width<centimetre, int>(2 * cm)));
static_assert(same(((2 * m) / height<metre, int>(3 * m)), quantity_kind<downcast_kind<height_kind, dim_one>, one, int>(0)));
static_assert(same(((2 * m) / height<metre, int>(3 * m)).common(), quantity(0)));
static_assert(same(((2 * m) / height<metre, double>(3. * m)).common(), quantity(2 / 3.)));
static_assert(same(((2. * m) / height<metre, int>(3 * m)).common(), quantity(2. / 3)));
static_assert(same(((2 * m) / height<metre, int>(3 * m) * (0 * m)), height<metre, int>(0 * m)));
static_assert(same(height<metre, int>(2 * m) / (3 * m),
quantity_kind<downcast_kind<height_kind, dim_one>, one, int>(0)));
static_assert(same(height<metre, int>(2 * m) / (3. * m),
quantity_kind<downcast_kind<height_kind, dim_one>, one, double>(2 / 3.)));
static_assert(same(height<metre, double>(2. * m) / (3 * m),
quantity_kind<downcast_kind<height_kind, dim_one>, one, double>(2. / 3)));
static_assert(same((2 * m) / height<metre, int>(3 * m),
quantity_kind<downcast_kind<height_kind, dim_one>, one, int>(0)));
static_assert(same((2 * m) / height<metre, double>(3. * m),
quantity_kind<downcast_kind<height_kind, dim_one>, one, double>(2 / 3.)));
static_assert(same((2. * m) / height<metre, int>(3 * m),
quantity_kind<downcast_kind<height_kind, dim_one>, one, double>(2. / 3)));
static_assert(same(width<metre, int>(8 * m) / width<metre, int>(2 * m),
quantity_kind<downcast_kind<width_kind, dim_one>, one, int>(4)));
static_assert(same(width<metre, int>(8 * m) / width<metre, double>(2 * m),
quantity_kind<downcast_kind<width_kind, dim_one>, one, double>(4.0)));
static_assert(same(width<metre, double>(8 * m) / width<metre, int>(2 * m),
quantity_kind<downcast_kind<width_kind, dim_one>, one, double>(4.0)));
static_assert(same(apples<one, int>(8) / apples<one, int>(2), apples<one, int>(4)));
static_assert(same(apples<one, int>(8) / (2 / apples<one, int>(1)), apples<one, int>(4)));
static_assert(same(horizontal_area<square_metre>(8 * m * m) / width<metre>(2 * m), width<metre>(4 * m)));
static_assert(same(width<metre, int>(2 * m) % 3, width<metre, int>(2 * m)));
static_assert(same(width<metre, int>(3 * m) % width<metre, int>(2 * m), width<metre, int>(1 * m)));
@@ -514,7 +634,6 @@ static_assert(is_same_v<
decltype((width<metre, std::uint8_t>(0 * m) % width<metre, std::uint8_t>(0 * m)).common().count()),
decltype(std::uint8_t(0) % std::uint8_t(0))>);
static_assert(!std::is_invocable_v<std::multiplies<>, width<metre>, width<metre>>);
static_assert(!std::is_invocable_v<std::multiplies<>, width<metre>, height<metre>>);
static_assert(!std::is_invocable_v<std::multiplies<>, height<metre>, quantity_point<dim_length, metre>>);
static_assert(!std::is_invocable_v<std::multiplies<>, quantity_point<dim_length, metre>, height<metre>>);

View File

@@ -336,7 +336,6 @@ static_assert(!constructible_or_convertible_from<nth_apple<percent, int>>(apples
static_assert(!constructible_or_convertible_from<nth_apple<percent, int>>(apples<percent, double>(dimensionless<percent>(1))));
static_assert(!constructible_or_convertible_from<nth_apple<one, int>>(apples<one, double>(1.0)));
static_assert(!constructible_or_convertible_from<nth_apple<one, int>>(apples<percent, double>(dimensionless<percent>(1))));
static_assert(!constructible_or_convertible_from<nth_apple<one, int>>(width<metre, int>(1 * m) / m));
static_assert(!constructible_or_convertible_from<nth_apple<one, int>>(oranges<one, int>(1)));
// clang-format on

View File

@@ -278,9 +278,10 @@ static_assert((1_q_m += 1_q_m).count() == 2);
static_assert((2_q_m -= 1_q_m).count() == 1);
static_assert((1_q_m *= 2).count() == 2);
static_assert((2_q_m /= 2).count() == 1);
static_assert((7_q_m %= 2).count() == 1);
static_assert((1_q_m *= quantity(2)).count() == 2);
static_assert((2_q_m /= quantity(2)).count() == 1);
static_assert((7_q_m %= 2).count() == 1);
static_assert((7_q_m %= quantity(2)).count() == 1);
static_assert((7_q_m %= 2_q_m).count() == 1);
// different types
@@ -295,8 +296,10 @@ static_assert((7.5_q_m /= quantity(3)).count() == 2.5);
static_assert((3500_q_m %= 1_q_km).count() == 500);
static_assert((std::uint8_t(255) * m %= 256).count() == [] { std::uint8_t ui(255); return ui %= 256; }());
static_assert((std::uint8_t(255) * m %= quantity(256)).count() == [] { std::uint8_t ui(255); return ui %= 256; }());
// static_assert((std::uint8_t(255) * m %= 256 * m).count() != [] { std::uint8_t ui(255); return ui %= 256; }()); // UB
static_assert((std::uint8_t(255) * m %= 257).count() == [] { std::uint8_t ui(255); return ui %= 257; }());
static_assert((std::uint8_t(255) * m %= quantity(257)).count() == [] { std::uint8_t ui(255); return ui %= 257; }());
// TODO: Fix
static_assert((std::uint8_t(255) * m %= 257 * m).count() != [] { std::uint8_t ui(255); return ui %= 257; }());
@@ -309,14 +312,21 @@ static_assert((22_q_m *= quantity(33.33)).count() == 733);
static_assert((22_q_m /= quantity(3.33)).count() == 6);
#endif
template<typename Metre>
template<typename Metre, typename Kilometre>
concept invalid_compound_assignments = requires() {
// truncating not allowed
requires !requires(length<Metre, int> l) { l += 2.5_q_m; };
requires !requires(length<Metre, int> l) { l -= 2.5_q_m; };
requires !requires(length<kilometre, int> l) { l += length<Metre, int>(2); };
requires !requires(length<kilometre, int> l) { l -= length<Metre, int>(2); };
requires !requires(length<kilometre, int> l) { l %= length<Metre, int>(2); };
requires !requires(length<Kilometre, int> l) { l += length<Metre, int>(2); };
requires !requires(length<Kilometre, int> l) { l -= length<Metre, int>(2); };
requires !requires(length<Kilometre, int> l) { l %= length<Metre, int>(2); };
requires !requires(length<Kilometre, int> l) { l %= dimensionless<percent, int>(2); };
requires !requires(length<Kilometre, int> l) { l %= dimensionless<percent, double>(2); };
// TODO: accept non-truncating argument
requires !requires(length<Kilometre, int> l) { l *= 1 * km / m; };
requires !requires(length<Kilometre, int> l) { l /= 1 * km / m; };
requires !requires(length<Kilometre, int> l) { l %= 1 * km / m; };
// only quantities can be added or subtracted
requires !requires(length<Metre, int> l) { l += 2; };
@@ -333,7 +343,7 @@ concept invalid_compound_assignments = requires() {
requires !requires(length<Metre, double> l) { l %= 2_q_m; };
requires !requires(length<Metre, int> l) { l %= 2._q_m; };
};
static_assert(invalid_compound_assignments<metre>);
static_assert(invalid_compound_assignments<metre, kilometre>);
////////////////////