Skip to content

Commit 6f3ef66

Browse files
committed
feat: comparisons against literal 0
Resolves #487
1 parent 7693360 commit 6f3ef66

File tree

9 files changed

+118
-94
lines changed

9 files changed

+118
-94
lines changed

docs/users_guide/framework_basics/quantity_arithmetics.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,10 @@ if (q1 / q2 != 0 * m / s)
293293
```
294294

295295
The above would work (assuming we are dealing with the quantity of speed) but could be
296-
suboptimal if the result of `q1 / q2` is not expressed in `m / s`. To eliminate the need
297-
for conversion, we need to write:
296+
suboptimal if the result of `q1 / q2` is not expressed in `m / s`. The library would
297+
need to rescale the arguments to the common unit before comparing if the value is zero.
298+
299+
To eliminate the need for conversion, we need to write:
298300

299301
```cpp
300302
if (auto q = q1 / q2; q != q.zero())
@@ -304,32 +306,36 @@ if (auto q = q1 / q2; q != q.zero())
304306
but that is a bit inconvenient, and inexperienced users could be unaware of this technique
305307
and its reasons.
306308

307-
For the above reasons, the library provides dedicated interfaces to compare against zero
308-
that follow the naming convention of
309-
[named comparison functions](https://en.cppreference.com/w/cpp/utility/compare/named_comparison_functions)
310-
in the C++ Standard Library. The _mp-units/compare.h_ header file exposes the following functions:
311-
312-
- `is_eq_zero`
313-
- `is_neq_zero`
314-
- `is_lt_zero`
315-
- `is_gt_zero`
316-
- `is_lteq_zero`
317-
- `is_gteq_zero`
309+
For the above reasons, the library provides special support for comparisons against the
310+
literal `0`. Only this one value has elevated privileges and does not have to state the
311+
unit — the numerical value zero is common to all scaled units of any kind.
318312

319-
Thanks to them, to save typing and not pay for unneeded conversions, our check could be
313+
Thanks to that, to save typing and not pay for unneeded conversions, our check could be
320314
implemented as follows:
321315

322316
```cpp
323-
if (is_neq_zero(q1 / q2))
317+
if (q1 / q2 != 0)
318+
// ...
319+
```
320+
321+
This works for all six comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`):
322+
323+
```cpp
324+
if (q > 0)
325+
// ...
326+
if (q <= 0)
324327
// ...
325328
```
326329

327330
!!! tip
328331

329-
Those functions will work with any type `T` that exposes `zero()` member function returning
330-
something comparable to `T`. Thanks to that, we can use them not only with quantities but also
331-
with [`std::chrono::duration`](https://en.cppreference.com/w/cpp/chrono/duration) or any other
332-
type that exposes such an interface.
332+
All six comparison operators support comparison against zero without specifying a unit.
333+
This works with any representation type `rep` for which
334+
`representation_values<rep>::zero()` is provided.
335+
336+
Only a compile-time zero is accepted: an integer or floating-point literal that is zero
337+
(e.g., `0`, `0.`, `0.f`, `0LL`). Passing another literal or a runtime variable — even one
338+
whose value happens to be zero — is rejected at compile time.
333339

334340

335341
## Other maths

example/include/geographic.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,8 @@ struct MP_UNITS_STD_FMT::formatter<geographic::latitude<T>, Char> :
141141
auto format(geographic::latitude<T> lat, FormatContext& ctx) const -> decltype(ctx.out())
142142
{
143143
const auto& q = lat.quantity_ref_from(geographic::equator);
144-
ctx.advance_to(
145-
formatter<typename geographic::latitude<T>::quantity_type, Char>::format(is_gteq_zero(q) ? q : -q, ctx));
146-
return MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " N" : "S");
144+
ctx.advance_to(formatter<typename geographic::latitude<T>::quantity_type, Char>::format(q >= 0 ? q : -q, ctx));
145+
return MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", q >= 0 ? " N" : "S");
147146
}
148147
};
149148

@@ -154,9 +153,8 @@ struct MP_UNITS_STD_FMT::formatter<geographic::longitude<T>, Char> :
154153
auto format(geographic::longitude<T> lon, FormatContext& ctx) const -> decltype(ctx.out())
155154
{
156155
const auto& q = lon.quantity_ref_from(geographic::prime_meridian);
157-
ctx.advance_to(
158-
formatter<typename geographic::longitude<T>::quantity_type, Char>::format(is_gteq_zero(q) ? q : -q, ctx));
159-
return MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " E" : " W");
156+
ctx.advance_to(formatter<typename geographic::longitude<T>::quantity_type, Char>::format(q >= 0 ? q : -q, ctx));
157+
return MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", q >= 0 ? " E" : " W");
160158
}
161159
};
162160

src/core/include/mp-units/framework/compare.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ template<typename T>
4040
requires requires {
4141
{ T::zero() } -> std::equality_comparable_with<T>;
4242
}
43-
[[nodiscard]] constexpr bool is_eq_zero(T v)
43+
[[deprecated("2.6.0: compare against literal `0` instead")]] [[nodiscard]] constexpr bool is_eq_zero(T v)
4444
{
4545
return v == T::zero();
4646
}
@@ -49,7 +49,7 @@ template<typename T>
4949
requires requires {
5050
{ T::zero() } -> std::equality_comparable_with<T>;
5151
}
52-
[[nodiscard]] constexpr bool is_neq_zero(T v)
52+
[[deprecated("2.6.0: compare against literal `0` instead")]] [[nodiscard]] constexpr bool is_neq_zero(T v)
5353
{
5454
return v != T::zero();
5555
}
@@ -58,7 +58,7 @@ template<typename T>
5858
requires requires {
5959
{ T::zero() } -> std::three_way_comparable_with<T>;
6060
}
61-
[[nodiscard]] constexpr bool is_lt_zero(T v)
61+
[[deprecated("2.6.0: compare against literal `0` instead")]] [[nodiscard]] constexpr bool is_lt_zero(T v)
6262
{
6363
return v < T::zero();
6464
}
@@ -67,7 +67,7 @@ template<typename T>
6767
requires requires {
6868
{ T::zero() } -> std::three_way_comparable_with<T>;
6969
}
70-
[[nodiscard]] constexpr bool is_gt_zero(T v)
70+
[[deprecated("2.6.0: compare against literal `0` instead")]] [[nodiscard]] constexpr bool is_gt_zero(T v)
7171
{
7272
return v > T::zero();
7373
}
@@ -76,7 +76,7 @@ template<typename T>
7676
requires requires {
7777
{ T::zero() } -> std::three_way_comparable_with<T>;
7878
}
79-
[[nodiscard]] constexpr bool is_lteq_zero(T v)
79+
[[deprecated("2.6.0: compare against literal `0` instead")]] [[nodiscard]] constexpr bool is_lteq_zero(T v)
8080
{
8181
return v <= T::zero();
8282
}
@@ -85,7 +85,7 @@ template<typename T>
8585
requires requires {
8686
{ T::zero() } -> std::three_way_comparable_with<T>;
8787
}
88-
[[nodiscard]] constexpr bool is_gteq_zero(T v)
88+
[[deprecated("2.6.0: compare against literal `0` instead")]] [[nodiscard]] constexpr bool is_gteq_zero(T v)
8989
{
9090
return v >= T::zero();
9191
}

src/core/include/mp-units/framework/quantity.h

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@
4848
import std;
4949
#else
5050
#include <compare> // IWYU pragma: export
51+
#include <concepts>
5152
#include <limits>
53+
#include <stdexcept>
54+
#include <type_traits>
5255
#include <utility>
5356
#if MP_UNITS_HOSTED
5457
#include <locale>
@@ -60,6 +63,15 @@ namespace mp_units {
6063

6164
namespace detail {
6265

66+
struct zero {
67+
template<typename T>
68+
requires std::is_arithmetic_v<T> && (!is_same_v<T, bool>)
69+
consteval explicit(false) zero(T val)
70+
{
71+
if (val != T{0}) MP_UNITS_THROW(std::logic_error("not zero"));
72+
}
73+
};
74+
6375
template<Unit UFrom, Unit UTo>
6476
[[nodiscard]] consteval bool integral_conversion_factor(UFrom from, UTo to)
6577
{
@@ -467,7 +479,7 @@ class quantity {
467479
}
468480
constexpr quantity& operator%=(const quantity<R2, Rep2>& other) &
469481
{
470-
MP_UNITS_EXPECTS_DEBUG(is_neq_zero(other));
482+
MP_UNITS_EXPECTS_DEBUG(other != 0);
471483
if constexpr (equivalent(unit, get_unit(R2)))
472484
numerical_value_is_an_implementation_detail_ %= other.numerical_value_is_an_implementation_detail_;
473485
else
@@ -574,7 +586,7 @@ class quantity {
574586
detail::CommonlyInvocableQuantities<std::modulus<>, quantity, quantity<R2, Rep2>>
575587
[[nodiscard]] friend constexpr Quantity auto operator%(const Q& lhs, const quantity<R2, Rep2>& rhs)
576588
{
577-
MP_UNITS_EXPECTS_DEBUG(is_neq_zero(rhs));
589+
MP_UNITS_EXPECTS_DEBUG(rhs != 0);
578590
using ret = detail::common_quantity_for<std::modulus<>, quantity, quantity<R2, Rep2>>;
579591
const ret ret_lhs(lhs);
580592
const ret ret_rhs(rhs);
@@ -625,7 +637,7 @@ class quantity {
625637
requires detail::InvocableQuantities<std::divides<>, quantity, quantity<R2, Rep2>>
626638
[[nodiscard]] friend constexpr Quantity auto operator/(const Q& lhs, const quantity<R2, Rep2>& rhs)
627639
{
628-
MP_UNITS_EXPECTS_DEBUG(is_neq_zero(rhs));
640+
MP_UNITS_EXPECTS_DEBUG(rhs != 0);
629641
return ::mp_units::quantity{lhs.numerical_value_ref_in(unit) / rhs.numerical_value_ref_in(rhs.unit), R / R2};
630642
}
631643

@@ -643,7 +655,7 @@ class quantity {
643655
(!Reference<Value>) && detail::InvokeResultOf<quantity_spec, std::divides<>, const Value&, rep>
644656
[[nodiscard]] friend constexpr Quantity auto operator/(const Value& val, const Q& q)
645657
{
646-
MP_UNITS_EXPECTS_DEBUG(is_neq_zero(q));
658+
MP_UNITS_EXPECTS_DEBUG(q != 0);
647659
return ::mp_units::quantity{val / q.numerical_value_ref_in(unit), one / R};
648660
}
649661

@@ -668,6 +680,13 @@ class quantity {
668680
return lhs.numerical_value_ref_in(unit) == rhs;
669681
}
670682

683+
template<std::derived_from<quantity> Q>
684+
requires std::equality_comparable<rep> && requires { representation_values<rep>::zero(); }
685+
[[nodiscard]] friend constexpr bool operator==(const Q& lhs, detail::zero)
686+
{
687+
return lhs.numerical_value_ref_in(unit) == representation_values<rep>::zero();
688+
}
689+
671690
template<std::derived_from<quantity> Q, auto R2, typename Rep2>
672691
requires requires { typename std::common_type_t<quantity, quantity<R2, Rep2>>; } &&
673692
std::three_way_comparable<typename std::common_type_t<quantity, quantity<R2, Rep2>>::rep>
@@ -685,6 +704,13 @@ class quantity {
685704
{
686705
return lhs.numerical_value_ref_in(unit) <=> rhs;
687706
}
707+
708+
template<std::derived_from<quantity> Q>
709+
requires std::three_way_comparable<rep> && requires { representation_values<rep>::zero(); }
710+
[[nodiscard]] friend constexpr auto operator<=>(const Q& lhs, detail::zero)
711+
{
712+
return lhs.numerical_value_ref_in(unit) <=> representation_values<rep>::zero();
713+
}
688714
};
689715

690716
// CTAD

src/core/include/mp-units/framework/quantity_point.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
#include <mp-units/bits/hacks.h>
2727
#include <mp-units/bits/module_macros.h>
2828
#include <mp-units/compat_macros.h>
29-
#include <mp-units/framework/compare.h>
3029
#include <mp-units/framework/customization_points.h>
3130
#include <mp-units/framework/quantity.h>
3231
#include <mp-units/framework/quantity_point_concepts.h>
@@ -109,9 +108,9 @@ struct point_origin_interface {
109108
is_derived_from_specialization_of_v<PO2, relative_point_origin>)
110109
return PO1::_quantity_point_ == PO2::_quantity_point_;
111110
else if constexpr (is_derived_from_specialization_of_v<PO1, relative_point_origin>)
112-
return detail::same_absolute_point_origins(po1, po2) && is_eq_zero(PO1::_quantity_point_.quantity_from_zero());
111+
return detail::same_absolute_point_origins(po1, po2) && PO1::_quantity_point_.quantity_from_zero() == 0;
113112
else if constexpr (is_derived_from_specialization_of_v<PO2, relative_point_origin>)
114-
return detail::same_absolute_point_origins(po1, po2) && is_eq_zero(PO2::_quantity_point_.quantity_from_zero());
113+
return detail::same_absolute_point_origins(po1, po2) && PO2::_quantity_point_.quantity_from_zero() == 0;
115114
}
116115
};
117116

src/systems/include/mp-units/systems/si/prefixes.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ template<Quantity Q, std::invocable<Q> Func, PrefixableUnit U, auto Character =
131131
constexpr decltype(auto) invoke_with_prefixed(Func func, Q q, U u, prefix_range range = prefix_range::engineering,
132132
int min_integral_digits = 1)
133133
{
134-
if (is_eq_zero(q)) return func(q.in(u));
134+
if (q == 0) return func(q.in(u));
135135

136136
using std::abs, std::log10, std::floor;
137137

test/static/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ add_library(
3434
angular_test.cpp
3535
astronomy_test.cpp
3636
cgs_test.cpp
37-
compare_test.cpp
3837
concepts_test.cpp
3938
# custom_rep_test_min_expl.cpp
4039
custom_rep_test_min_impl.cpp

test/static/compare_test.cpp

Lines changed: 0 additions & 54 deletions
This file was deleted.

test/static/quantity_test.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,24 @@ static_assert(v{1., 2., 3.} * one == v{1., 2., 3.});
13091309
static_assert(v{1., 2., 3.} != v{3., 2., 1.} * one);
13101310
#endif
13111311

1312+
static_assert(0 * m == 0);
1313+
static_assert(0. * m == 0);
1314+
static_assert(0 * m == 0.);
1315+
static_assert(0. * m == 0.);
1316+
static_assert(42 * m != 0);
1317+
static_assert(42. * m != 0);
1318+
static_assert(42 * m != 0.);
1319+
static_assert(42. * m != 0.);
1320+
1321+
static_assert(!(42 * m == 0));
1322+
static_assert(!(42. * m == 0));
1323+
static_assert(!(42 * m == 0.));
1324+
static_assert(!(42. * m == 0.));
1325+
static_assert(!(0 * m != 0));
1326+
static_assert(!(0. * m != 0));
1327+
static_assert(!(0 * m != 0.));
1328+
static_assert(!(0. * m != 0.));
1329+
13121330
///////////////////////
13131331
// ordering operators
13141332
///////////////////////
@@ -1366,6 +1384,38 @@ static_assert(!(123 * km >= 321'000 * m));
13661384
static_assert(1 * one < 2);
13671385
static_assert(1 < 2 * one);
13681386

1387+
static_assert(42 * m > 0);
1388+
static_assert(42. * m > 0);
1389+
static_assert(42 * m > 0.);
1390+
static_assert(42. * m > 0.);
1391+
static_assert(-42 * m < 0);
1392+
static_assert(-42. * m < 0);
1393+
static_assert(-42 * m < 0.);
1394+
static_assert(-42. * m < 0.);
1395+
1396+
static_assert(!(-42 * m > 0));
1397+
static_assert(!(-42. * m > 0));
1398+
static_assert(!(-42 * m > 0.));
1399+
static_assert(!(-42. * m > 0.));
1400+
static_assert(!(42 * m < 0));
1401+
static_assert(!(42. * m < 0));
1402+
static_assert(!(42 * m < 0.));
1403+
static_assert(!(42. * m < 0.));
1404+
1405+
static_assert(42 * m >= 0);
1406+
static_assert(42. * m >= 0);
1407+
static_assert(42 * m >= 0.);
1408+
static_assert(42. * m >= 0.);
1409+
static_assert(0 * m >= 0);
1410+
static_assert(-42 * m <= 0);
1411+
static_assert(-42. * m <= 0);
1412+
static_assert(-42 * m <= 0.);
1413+
static_assert(-42. * m <= 0.);
1414+
static_assert(0 * m <= 0);
1415+
1416+
static_assert(!(-42 * m >= 0));
1417+
static_assert(!(42 * m <= 0));
1418+
13691419

13701420
//////////////////
13711421
// dimensionless

0 commit comments

Comments
 (0)