Arene Base
Fundamental Utilities For Safety Critical C++
Loading...
Searching...
No Matches
safe_comparisons.hpp
Go to the documentation of this file.
1// Copyright 2026, Toyota Motor Corporation
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5///
6/// @file safe_comparisons.hpp
7/// @brief Provides backports of the safe integer comparison functions from C++20
8///
9#ifndef INCLUDE_GUARD_ARENE_BASE_ARENE_BASE_UTILITY_SAFE_COMPARISONS_HPP_
10#define INCLUDE_GUARD_ARENE_BASE_ARENE_BASE_UTILITY_SAFE_COMPARISONS_HPP_
11
12// IWYU pragma: private, include "arene/base/utility.hpp"
13// IWYU pragma: friend "(arene/base(?!/tests)|stdlib/include/stdlib_detail)/.*"
14
15// parasoft-begin-suppress AUTOSAR-A16_2_2-a-2 "Arene Base aggregate headers permitted by A16-2-2 Permit #1"
16#include "arene/base/compiler_support/attributes.hpp"
17#include "arene/base/compiler_support/cpp14_inline.hpp"
18#include "arene/base/constraints/constraints.hpp"
19#include "arene/base/stdlib_choice/enable_if.hpp"
20#include "arene/base/stdlib_choice/is_integral.hpp"
21#include "arene/base/stdlib_choice/is_signed.hpp"
22#include "arene/base/stdlib_choice/is_unsigned.hpp"
23#include "arene/base/stdlib_choice/make_unsigned.hpp"
24// parasoft-end-suppress AUTOSAR-A16_2_2-a-2
25
26// parasoft-begin-suppress AUTOSAR-M4_5_3-a "Type is used as an integral type here, not a character"
27
28namespace arene {
29namespace base {
30
31namespace safe_comparisons_detail {
32
33///
34/// @brief Helper to determine if two integral types have the same signedness
35///
36/// @tparam T One of the types
37/// @tparam U The other type
38/// @return bool Equivalent to @c std::is_signed<T>::value==std::is_signed<U>::value
39///
40template <typename T, typename U>
41static constexpr bool is_same_signedness_v = std::is_signed<T>::value == std::is_signed<U>::value;
42
43///
44/// @brief Compares two operands of the same signdness for equality
45///
46/// @tparam T Type of the left hand operand.
47/// @tparam U Type of the right hand operand.
48/// @param lhs The left hand operand.
49/// @param rhs The right hand operand.
50/// @return bool Equivalent to direct comparison of the operands.
51///
52template <typename T, typename U, constraints<std::enable_if_t<is_same_signedness_v<T, U>>> = nullptr>
53ARENE_NODISCARD constexpr auto do_cmp_equal(T const lhs, U const rhs) noexcept -> bool {
54 return lhs == rhs;
55}
56
57///
58/// @brief Compares a signed operand with an unsigned operand for equality.
59///
60/// @tparam T Type of the left hand operand.
61/// @tparam U Type of the right hand operand.
62/// @param lhs The left hand operand.
63/// @param rhs The right hand operand.
64/// @return false If @c lhs<0, or if @c lhs==rhs is false when @c lhs is cast to its unsigned equivalent.
65/// @return true If @c lhs>=0 and @c lhs==rhs when @c lhs is cast to its unsigned equivalent.
66///
67template <
68 typename T,
69 typename U,
70 constraints<std::enable_if_t<std::is_signed<T>::value>, std::enable_if_t<std::is_unsigned<U>::value>> = nullptr>
71ARENE_NODISCARD constexpr auto do_cmp_equal(T const lhs, U const rhs) noexcept -> bool {
72 if (lhs < 0) {
73 return false;
74 }
75 return static_cast<std::make_unsigned_t<T>>(lhs) == rhs;
76}
77
78///
79/// @brief Compares a unsigned operand with an signed operand for equality.
80///
81/// @tparam T Type of the left hand operand.
82/// @tparam U Type of the right hand operand.
83/// @param lhs The left hand operand.
84/// @param rhs The right hand operand.
85/// @return false If @c rhs<0, or if @c lhs==rhs is false when @c rhs is cast to its unsigned equivalent.
86/// @return true If @c rhs>=0 and @c lhs==rhs when @c rhs is cast to its unsigned equivalent.
87///
88template <
89 typename T,
90 typename U,
91 constraints<std::enable_if_t<std::is_unsigned<T>::value>, std::enable_if_t<std::is_signed<U>::value>> = nullptr>
92ARENE_NODISCARD constexpr auto do_cmp_equal(T const lhs, U const rhs) noexcept -> bool {
93 return do_cmp_equal(rhs, lhs);
94}
95
96///
97/// @brief Compares two operands of the same signdness for less-than
98///
99/// @tparam T Type of the left hand operand.
100/// @tparam U Type of the right hand operand.
101/// @param lhs The left hand operand.
102/// @param rhs The right hand operand.
103/// @return bool Equivalent to direct comparison of the operands.
104///
105template <typename T, typename U, constraints<std::enable_if_t<is_same_signedness_v<T, U>>> = nullptr>
106ARENE_NODISCARD constexpr auto do_cmp_less(T const lhs, U const rhs) noexcept -> bool {
107 // parasoft-begin-suppress AUTOSAR-M4_5_1-a "This function handles generic integer types, including bool"
108 // parasoft-begin-suppress CERT_C-EXP46-b "This function handles generic integer types, including bool"
109 return lhs < rhs;
110 // parasoft-end-suppress AUTOSAR-M4_5_1-a
111 // parasoft-end-suppress CERT_C-EXP46-b
112}
113
114///
115/// @brief Compares a signed operand with an unsigned operand for less-than.
116///
117/// @tparam T Type of the left hand operand.
118/// @tparam U Type of the right hand operand.
119/// @param lhs The left hand operand.
120/// @param rhs The right hand operand.
121/// @return false If @c lhs>=0 and @c lhs<rhs is false when @c lhs is cast to its unsigned equivalent.
122/// @return true If @c lhs<0 or @c lhs<rhs when @c lhs is cast to its unsigned equivalent.
123///
124template <
125 typename T,
126 typename U,
127 constraints<std::enable_if_t<std::is_signed<T>::value>, std::enable_if_t<std::is_unsigned<U>::value>> = nullptr>
128ARENE_NODISCARD constexpr auto do_cmp_less(T const lhs, U const rhs) noexcept -> bool {
129 if (lhs < 0) {
130 return true;
131 }
132 // parasoft-begin-suppress AUTOSAR-M4_5_1-a "This function handles generic integer types, including bool"
133 // parasoft-begin-suppress CERT_C-EXP46-b "This function handles generic integer types, including bool"
134 return static_cast<std::make_unsigned_t<T>>(lhs) < rhs;
135 // parasoft-end-suppress AUTOSAR-M4_5_1-a
136 // parasoft-end-suppress CERT_C-EXP46-b
137}
138
139///
140/// @brief Compares a signed operand with an unsigned operand for less-than.
141///
142/// @tparam T Type of the left hand operand.
143/// @tparam U Type of the right hand operand.
144/// @param lhs The left hand operand.
145/// @param rhs The right hand operand.
146/// @return false If @c rhs<0 or @c lhs<rhs is false when @c rhs is cast to its unsigned equivalent.
147/// @return true If @c rhs>=0 and @c lhs<rhs when @c rhs is cast to its unsigned equivalent.
148///
149template <
150 typename T,
151 typename U,
152 constraints<std::enable_if_t<std::is_unsigned<T>::value>, std::enable_if_t<std::is_signed<U>::value>> = nullptr>
153ARENE_NODISCARD constexpr auto do_cmp_less(T const lhs, U const rhs) noexcept -> bool {
154 if (rhs < 0) {
155 return false;
156 }
157 // parasoft-begin-suppress AUTOSAR-M4_5_1-a "This function handles generic integer types, including bool"
158 // parasoft-begin-suppress CERT_C-EXP46-b "This function handles generic integer types, including bool"
159 return lhs < static_cast<std::make_unsigned_t<U>>(rhs);
160 // parasoft-end-suppress AUTOSAR-M4_5_1-a
161 // parasoft-end-suppress CERT_C-EXP46-b
162}
163
164///
165/// @brief Function object that implements @c cmp_equal
166///
167class cmp_equal_fn {
168 public:
169 ///
170 /// @brief Compares two integral types for equality in a mixed-signed-unsigned-type safe manner.
171 ///
172 /// If either @c T or @c U do not have the same signedness, then if the signed-value is non-negative, it is converted
173 /// into an unsigned type via @c std::make_unsigned_t to perform the comparison.
174 ///
175 /// @tparam T Type of the left hand operand.
176 /// @tparam U Type of the right hand operand.
177 /// @param lhs The left hand operand.
178 /// @param rhs The right hand operand.
179 /// @return false If @c T and @c U do not have the same signed-ness and the corresponding operand is negative, or
180 /// common-frame @c lhs==rhs is @c false .
181 /// @return true If @c T and @c U are both positive and common-frame @c lhs==rhs is @c true .
182 ///
183 template <
184 typename T,
185 typename U,
186 constraints<std::enable_if_t<std::is_integral<T>::value>, std::enable_if_t<std::is_integral<U>::value>> = nullptr>
187 ARENE_NODISCARD constexpr auto operator()(T const lhs, U const rhs) const noexcept -> bool {
188 return do_cmp_equal(lhs, rhs);
189 }
190};
191
192///
193/// @brief Implementation helper for @c cmp_not_equal
194///
195class cmp_not_equal_fn {
196 public:
197 ///
198 /// @brief Compares two integral types for inequality in a mixed-signed-unsigned-type safe manner.
199 ///
200 /// If either @c T or @c U do not have the same signedness, then if the signed-value is non-negative, it is converted
201 /// into an unsigned type via @c std::make_unsigned_t to perform the comparison.
202 ///
203 /// @tparam T Type of the left hand operand.
204 /// @tparam U Type of the right hand operand.
205 /// @param lhs The left hand operand.
206 /// @param rhs The right hand operand.
207 /// @return false If @c T and @c U are both positive and common-frame @c lhs!=rhs is @c false .
208 /// @return true If @c T and @c U do not have the same signed-ness and the corresponding operand is negative, or
209 /// common-frame @c lhs!=rhs is @c true .
210 ///
211 template <
212 typename T,
213 typename U,
214 constraints<std::enable_if_t<std::is_integral<T>::value>, std::enable_if_t<std::is_integral<U>::value>> = nullptr>
215 ARENE_NODISCARD constexpr auto operator()(T const lhs, U const rhs) const noexcept -> bool {
216 return !do_cmp_equal(lhs, rhs);
217 }
218};
219
220///
221/// @brief Implementation helper for @c cmp_less
222///
223class cmp_less_fn {
224 public:
225 ///
226 /// @brief Compares two integral types for less-than in a mixed-signed-unsigned-type safe manner.
227 ///
228 /// If either @c T or @c U do not have the same signedness, then if the signed-value is non-negative, it is converted
229 /// into an unsigned type via @c std::make_unsigned_t to perform the comparison.
230 ///
231 /// @tparam T Type of the left hand operand.
232 /// @tparam U Type of the right hand operand.
233 /// @param lhs The left hand operand.
234 /// @param rhs The right hand operand.
235 /// @return false If @c U is signed and negative while @c T is unsigned, or if common-frame @c lhs<rhs is false
236 /// @return true If @c T is signed and negative while @c U is unsigned, or if common-frame @c lhs<rhs is true
237 ///
238 template <
239 typename T,
240 typename U,
241 constraints<std::enable_if_t<std::is_integral<T>::value>, std::enable_if_t<std::is_integral<U>::value>> = nullptr>
242 ARENE_NODISCARD constexpr auto operator()(T const lhs, U const rhs) const noexcept -> bool {
243 return do_cmp_less(lhs, rhs);
244 }
245};
246
247///
248/// @brief Implementation helper for @c cmp_greater
249///
250class cmp_greater_fn {
251 public:
252 ///
253 /// @brief Compares two integral types for greater-than in a mixed-signed-unsigned-type safe manner.
254 ///
255 /// If either @c T or @c U do not have the same signedness, then if the signed-value is non-negative, it is converted
256 /// into an unsigned type via @c std::make_unsigned_t to perform the comparison.
257 ///
258 /// @tparam T Type of the left hand operand.
259 /// @tparam U Type of the right hand operand.
260 /// @param lhs The left hand operand.
261 /// @param rhs The right hand operand.
262 /// @return false If @c T is signed and negative while @c U is unsigned, or if common-frame @c lhs>rhs is false
263 /// @return true If @c U is signed and negative while @c T is unsigned, or if common-frame @c lhs>rhs is true
264 ///
265 template <
266 typename T,
267 typename U,
268 constraints<std::enable_if_t<std::is_integral<T>::value>, std::enable_if_t<std::is_integral<U>::value>> = nullptr>
269 ARENE_NODISCARD constexpr auto operator()(T const lhs, U const rhs) const noexcept -> bool {
270 return do_cmp_less(rhs, lhs);
271 }
272};
273
274///
275/// @brief Implementation helper for @c cmp_less_equal_fn
276///
277class cmp_less_equal_fn {
278 public:
279 ///
280 /// @brief Compares two integral types for less-than or equals in a mixed-signed-unsigned-type safe manner.
281 ///
282 /// If either @c T or @c U do not have the same signedness, then if the signed-value is non-negative, it is converted
283 /// into an unsigned type via @c std::make_unsigned_t to perform the comparison.
284 ///
285 /// @tparam T Type of the left hand operand.
286 /// @tparam U Type of the right hand operand.
287 /// @param lhs The left hand operand.
288 /// @param rhs The right hand operand.
289 /// @return false If @c U is signed and negative while @c T is unsigned, or if common-frame @c lhs<=rhs is false
290 /// @return true If @c T is signed and negative while @c U is unsigned, or if common-frame @c lhs<=rhs is true
291 ///
292 template <
293 typename T,
294 typename U,
295 constraints<std::enable_if_t<std::is_integral<T>::value>, std::enable_if_t<std::is_integral<U>::value>> = nullptr>
296 ARENE_NODISCARD constexpr auto operator()(T const lhs, U const rhs) const noexcept -> bool {
297 return !do_cmp_less(rhs, lhs);
298 }
299};
300
301///
302/// @brief Implementation helper for @c cmp_greater_equal_fn
303///
304class cmp_greater_equal_fn {
305 public:
306 ///
307 /// @brief Compares two integral types for greater-than or equals in a mixed-signed-unsigned-type safe manner.
308 ///
309 /// If either @c T or @c U do not have the same signedness, then if the signed-value is non-negative, it is converted
310 /// into an unsigned type via @c std::make_unsigned_t to perform the comparison.
311 ///
312 /// @tparam T Type of the left hand operand.
313 /// @tparam U Type of the right hand operand.
314 /// @param lhs The left hand operand.
315 /// @param rhs The right hand operand.
316 /// @return false If @c T is signed and negative while @c U is unsigned, or if common-frame @c lhs>=rhs is false
317 /// @return true If @c U is signed and negative while @c T is unsigned, or if common-frame @c lhs>=rhs is true
318 ///
319 template <
320 typename T,
321 typename U,
322 constraints<std::enable_if_t<std::is_integral<T>::value>, std::enable_if_t<std::is_integral<U>::value>> = nullptr>
323 ARENE_NODISCARD constexpr auto operator()(T const lhs, U const rhs) const noexcept -> bool {
324 return !do_cmp_less(lhs, rhs);
325 }
326};
327
328} // namespace safe_comparisons_detail
329
330// parasoft-begin-suppress AUTOSAR-M7_3_3-a "An unnamed namespace is used to create a per-TU reference to a global
331// object used in multiple TUs."
332// parasoft-begin-suppress CERT_CPP-DCL59-a "An unnamed namespace is used to create a per-TU reference to a global
333// object used in multiple TUs."
334/// @def arene::base::cmp_equal
335/// @copydoc arene::base::safe_comparisons_detail::cmp_equal_fn::operator()
336ARENE_CPP14_INLINE_VARIABLE(safe_comparisons_detail::cmp_equal_fn, cmp_equal);
337
338/// @def arene::base::cmp_not_equal
339/// @copydoc arene::base::safe_comparisons_detail::cmp_not_equal::operator()
340ARENE_CPP14_INLINE_VARIABLE(safe_comparisons_detail::cmp_not_equal_fn, cmp_not_equal);
341
342/// @def arene::base::cmp_less
343/// @copydoc arene::base::safe_comparisons_detail::cmp_less::operator()
344ARENE_CPP14_INLINE_VARIABLE(safe_comparisons_detail::cmp_less_fn, cmp_less);
345
346/// @def arene::base::cmp_less_equal
347/// @copydoc arene::base::safe_comparisons_detail::cmp_less_equal::operator()
348ARENE_CPP14_INLINE_VARIABLE(safe_comparisons_detail::cmp_less_equal_fn, cmp_less_equal);
349
350/// @def arene::base::cmp_greater
351/// @copydoc arene::base::safe_comparisons_detail::cmp_greater::operator()
352ARENE_CPP14_INLINE_VARIABLE(safe_comparisons_detail::cmp_greater_fn, cmp_greater);
353
354/// @def arene::base::cmp_greater_equal
355/// @copydoc arene::base::safe_comparisons_detail::cmp_greater_equal::operator()
356ARENE_CPP14_INLINE_VARIABLE(safe_comparisons_detail::cmp_greater_equal_fn, cmp_greater_equal);
357// parasoft-end-suppress AUTOSAR-M7_3_3-a
358// parasoft-end-suppress CERT_CPP-DCL59-a
359
360} // namespace base
361} // namespace arene
362
363// parasoft-end-suppress AUTOSAR-M4_5_3-a
364
365#endif // INCLUDE_GUARD_ARENE_BASE_ARENE_BASE_UTILITY_SAFE_COMPARISONS_HPP_
Definition array_exceptions_disabled.cpp:11
Copyright 2026, Toyota Motor Corporation.
Definition array_exceptions_disabled.cpp:10