HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
cast.hpp
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "utility.hpp"
8#include "concepts.hpp"
9#include "assert.hpp"
10#include "compare.hpp"
11#include <type_traits>
12#include <concepts>
13#include <climits>
14
15hi_warning_push();
16// C26472: Don't use static_cast for arithmetic conversions, Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
17// This file implements narrow_cast().
18hi_warning_ignore_msvc(26472);
19// C26467: Converting from floating point to unsigned integral types results in non-portable code if the double/float has
20// a negative value. Use gsl::narrow_cast or gsl::naroow instead to guard against undefined behavior and potential data loss
21// (es.46).
22// This file implements narrow_cast().
23hi_warning_ignore_msvc(26467);
24// C26496: The variable 'r' does not change after construction, mark it as const (con.4).
25// False positive
26hi_warning_ignore_msvc(26496);
27
28namespace hi::inline v1 {
29template<typename T>
30[[nodiscard]] constexpr T copy(T value) noexcept
31{
32 return value;
33}
34
37template<typename Out, std::derived_from<std::remove_pointer_t<Out>> In>
38[[nodiscard]] constexpr Out up_cast(In *rhs) noexcept
39 requires std::is_pointer_v<Out> and
40 (std::is_const_v<std::remove_pointer_t<Out>> == std::is_const_v<In> or std::is_const_v<std::remove_pointer_t<Out>>)
41{
42 return static_cast<Out>(rhs);
43}
44
47template<typename Out>
48[[nodiscard]] constexpr Out up_cast(nullptr_t) noexcept
49 requires std::is_pointer_v<Out>
50{
51 return nullptr;
52}
53
56template<typename Out, std::derived_from<std::remove_reference_t<Out>> In>
57[[nodiscard]] constexpr Out up_cast(In& rhs) noexcept
58 requires std::is_reference_v<Out> and
59 (std::is_const_v<std::remove_reference_t<Out>> == std::is_const_v<In> or std::is_const_v<std::remove_reference_t<Out>>)
60{
61 return static_cast<Out>(rhs);
62}
63
71template<typename Out, base_of<std::remove_pointer_t<Out>> In>
72[[nodiscard]] constexpr Out down_cast(In *rhs) noexcept
73 requires std::is_pointer_v<Out> and
74 (std::is_const_v<std::remove_pointer_t<Out>> == std::is_const_v<In> or std::is_const_v<std::remove_pointer_t<Out>>)
75{
76 hi_axiom(rhs == nullptr or dynamic_cast<Out>(rhs) != nullptr);
77 return static_cast<Out>(rhs);
78}
79
84template<typename Out>
85[[nodiscard]] constexpr Out down_cast(nullptr_t) noexcept
86 requires std::is_pointer_v<Out>
87{
88 return nullptr;
89}
90
97template<typename Out, base_of<std::remove_reference_t<Out>> In>
98[[nodiscard]] constexpr Out down_cast(In& rhs) noexcept
99 requires std::is_reference_v<Out> and
100 (std::is_const_v<std::remove_reference_t<Out>> == std::is_const_v<In> or std::is_const_v<std::remove_reference_t<Out>>)
101{
102 hi_axiom(dynamic_cast<std::add_pointer_t<std::remove_reference_t<Out>>>(std::addressof(rhs)) != nullptr);
103 return static_cast<Out>(rhs);
104}
105
108template<arithmetic Out, arithmetic In>
109[[nodiscard]] constexpr Out wide_cast(In rhs) noexcept
110 requires(type_in_range_v<Out, In>)
111{
112 return static_cast<Out>(rhs);
113}
114
117template<arithmetic Out>
118[[nodiscard]] constexpr Out wide_cast(bool rhs) noexcept
119{
120 return static_cast<Out>(rhs);
121}
122
123namespace detail {
124
125template<arithmetic Out, arithmetic In>
126[[nodiscard]] constexpr bool narrow_validate(Out out, In in) noexcept
127{
128 // in- and out-value compares the same, after converting out-value back to in-type.
129 auto r = (in == static_cast<In>(out));
130
131 // If the types have different signs we need to do an extra test to make sure the actual sign
132 // of the values are the same as well.
134 r &= (in < In{}) == (out < Out{});
135 }
136
137 return r;
138}
139
140} // namespace detail
141
148template<std::integral Out, arithmetic In>
149[[nodiscard]] constexpr Out saturate_cast(In rhs) noexcept
150{
151 if constexpr (std::is_floating_point_v<In>) {
152 if (std::isnan(rhs)) {
153 return Out{0};
154 }
155 }
156
157 if (three_way_compare(rhs, std::numeric_limits<Out>::lowest()) != std::strong_ordering::greater) {
159 } else if (three_way_compare(rhs, std::numeric_limits<Out>::max()) != std::strong_ordering::less) {
161 } else {
162 return static_cast<Out>(rhs);
163 }
164}
165
174template<typename Out, typename In>
175[[nodiscard]] constexpr Out narrow_cast(In const &rhs) noexcept;
176
177template<arithmetic Out, arithmetic In>
178[[nodiscard]] constexpr Out narrow_cast(In const &rhs) noexcept
179{
180 if constexpr (type_in_range_v<Out, In>) {
181 return static_cast<Out>(rhs);
182 } else {
183 hilet r = static_cast<Out>(rhs);
184 hi_axiom(detail::narrow_validate(r, rhs));
185 return r;
186 }
187}
188
189template<arithmetic Out, arithmetic In>
190[[nodiscard]] constexpr Out round_cast(In rhs) noexcept
191{
192 if constexpr (std::is_floating_point_v<In>) {
193 return narrow_cast<Out>(std::round(rhs));
194 } else {
195 return narrow_cast<Out>(rhs);
196 }
197}
198
199template<arithmetic Out, arithmetic In>
200[[nodiscard]] constexpr Out floor_cast(In rhs) noexcept
201{
202 if constexpr (std::is_floating_point_v<In>) {
203 return narrow_cast<Out>(std::floor(rhs));
204 } else {
205 return narrow_cast<Out>(rhs);
206 }
207}
208
209template<arithmetic Out, arithmetic In>
210[[nodiscard]] constexpr Out ceil_cast(In rhs) noexcept
211{
212 if constexpr (std::is_floating_point_v<In>) {
213 return narrow_cast<Out>(std::ceil(rhs));
214 } else {
215 return narrow_cast<Out>(rhs);
216 }
217}
218
219template<std::integral Out, arithmetic In>
220[[nodiscard]] constexpr Out truncate(In rhs) noexcept
221{
222 return static_cast<Out>(rhs);
223}
224
235template<std::integral Out, std::integral In>
236[[nodiscard]] constexpr Out char_cast(In rhs) noexcept
237{
238 using in_unsigned_type = std::make_unsigned_t<In>;
239 using out_unsigned_type = std::make_unsigned_t<Out>;
240
241 // We cast to unsigned of the same type, so that we don't accidentally sign extent 'char'.
242 auto in_unsigned = static_cast<in_unsigned_type>(rhs);
243 auto out_unsigned = narrow_cast<out_unsigned_type>(in_unsigned);
244 return static_cast<Out>(out_unsigned);
245}
246
247template<std::integral Out>
248[[nodiscard]] constexpr Out char_cast(std::byte rhs) noexcept
249{
250 return char_cast<Out>(static_cast<uint8_t>(rhs));
251}
252
255template<std::unsigned_integral OutType, std::unsigned_integral InType>
256[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
257{
258 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of low_bit_cast must be half the size of the input");
259 return static_cast<OutType>(value);
260}
261
264template<std::unsigned_integral OutType, std::unsigned_integral InType>
265[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
266{
267 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of high_bit_cast must be half the size of the input");
268 return static_cast<OutType>(value >> sizeof(OutType) * CHAR_BIT);
269}
270
273template<std::signed_integral OutType, std::signed_integral InType>
274[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
275{
276 using UInType = std::make_unsigned_t<InType>;
277 using UOutType = std::make_unsigned_t<OutType>;
278 return static_cast<OutType>(low_bit_cast<UOutType>(static_cast<UInType>(value)));
279}
280
283template<std::signed_integral OutType, std::signed_integral InType>
284[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
285{
286 using UInType = std::make_unsigned_t<InType>;
287 using UOutType = std::make_unsigned_t<OutType>;
288 return static_cast<OutType>(high_bit_cast<UOutType>(static_cast<UInType>(value)));
289}
290
293template<std::unsigned_integral OutType, std::signed_integral InType>
294[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
295{
296 using UInType = std::make_unsigned_t<InType>;
297 return low_bit_cast<OutType>(static_cast<UInType>(value));
298}
299
302template<std::unsigned_integral OutType, std::signed_integral InType>
303[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
304{
305 using UInType = std::make_unsigned_t<InType>;
306 return high_bit_cast<OutType>(static_cast<UInType>(value));
307}
308
311template<std::unsigned_integral OutType, std::unsigned_integral InType>
312[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
313{
314 static_assert(sizeof(OutType) == sizeof(InType) * 2, "Return value of merge_bit_cast must be double the size of the input");
315
316 OutType r = static_cast<OutType>(hi);
317 r <<= sizeof(InType) * CHAR_BIT;
318 r |= static_cast<OutType>(lo);
319 return r;
320}
321
324template<std::signed_integral OutType, std::signed_integral InType>
325[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
326{
327 using UInType = std::make_unsigned_t<InType>;
328 using UOutType = std::make_unsigned_t<OutType>;
329 return static_cast<OutType>(merge_bit_cast<UOutType>(static_cast<UInType>(hi), static_cast<UInType>(lo)));
330}
331
334template<std::signed_integral OutType, std::unsigned_integral InType>
335[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
336{
337 using UOutType = std::make_unsigned_t<OutType>;
338 return narrow_cast<OutType>(merge_bit_cast<UOutType>(hi, lo));
339}
340
341[[nodiscard]] constexpr auto to_underlying(scoped_enum auto rhs) noexcept
342{
343 return static_cast<std::underlying_type_t<decltype(rhs)>>(rhs);
344}
345
346template<typename T>
347[[nodiscard]] constexpr bool to_bool(T&& rhs) noexcept
348 requires(requires(T&& x) { static_cast<bool>(std::forward<T>(x)); })
349{
350 return static_cast<bool>(std::forward<T>(rhs));
351}
352
353template<typename T>
354[[nodiscard]] inline T to_ptr(std::intptr_t value) noexcept
355 requires std::is_pointer_v<T>
356{
357 return reinterpret_cast<T>(value);
358}
359
360template<typename T>
361[[nodiscard]] inline std::intptr_t to_int(T *ptr) noexcept
362{
363 return reinterpret_cast<std::intptr_t>(ptr);
364}
365
366} // namespace hi::inline v1
367
368hi_warning_pop();
Utilities to assert and bound check.
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:133
Utilities used by the HikoGUI library itself.
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
DOXYGEN BUG.
Definition algorithm.hpp:15
constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
Return the upper half of the input value.
Definition cast.hpp:312
constexpr Out wide_cast(In rhs) noexcept
Cast a number to a type that will be able to represent all values without loss of precision.
Definition cast.hpp:109
constexpr OutType high_bit_cast(InType value) noexcept
Return the upper half of the input value.
Definition cast.hpp:265
constexpr Out saturate_cast(In rhs) noexcept
Cast a numeric value to an integer saturating on overflow.
Definition cast.hpp:149
constexpr Out up_cast(In *rhs) noexcept
Cast a pointer to a class to its base class or itself.
Definition cast.hpp:38
constexpr Out char_cast(In rhs) noexcept
Cast a character.
Definition cast.hpp:236
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:178
constexpr OutType low_bit_cast(InType value) noexcept
Return the low half of the input value.
Definition cast.hpp:256
constexpr Out down_cast(In *rhs) noexcept
Cast a pointer to a class to its derived class or itself.
Definition cast.hpp:72
geometry/margins.hpp
Definition assert.hpp:18
constexpr auto three_way_compare(Lhs const &lhs, Rhs const &rhs) noexcept
Safely compare two arithmetic values to each other.
Definition compare.hpp:123
T addressof(T... args)
T ceil(T... args)
T copy(T... args)
T floor(T... args)
T isnan(T... args)
T lowest(T... args)
T max(T... args)
T round(T... args)