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 "type_traits.hpp"
9#include "concepts.hpp"
10#include "assert.hpp"
11#include "compare.hpp"
12#include <type_traits>
13#include <concepts>
14#include <climits>
15#include <span>
16
17hi_warning_push();
18// C26472: Don't use static_cast for arithmetic conversions, Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
19// This file implements narrow_cast().
20hi_warning_ignore_msvc(26472);
21// C26467: Converting from floating point to unsigned integral types results in non-portable code if the double/float has
22// a negative value. Use gsl::narrow_cast or gsl::naroow instead to guard against undefined behavior and potential data loss
23// (es.46).
24// This file implements narrow_cast().
25hi_warning_ignore_msvc(26467);
26// C26496: The variable 'r' does not change after construction, mark it as const (con.4).
27// False positive
28hi_warning_ignore_msvc(26496);
29// C26466: Don't use static_cast downcast. A cast from a polymorphic type should use dynamic_cast (type.2)
30// Used in down_cast<>() specifically for doing this savely.
31hi_warning_ignore_msvc(26466);
32// C26474: Don't cast between pointer types when the conversion could be implicit (type.1).
33// Since these functions are templates this happens.
34hi_warning_ignore_msvc(26474);
35
36namespace hi::inline v1 {
37template<typename T>
38[[nodiscard]] constexpr T copy(T value) noexcept
39{
40 return value;
41}
42
45template<typename Out, std::derived_from<std::remove_pointer_t<Out>> In>
46[[nodiscard]] constexpr Out up_cast(In *rhs) noexcept
47 requires std::is_pointer_v<Out> and
48 (std::is_const_v<std::remove_pointer_t<Out>> == std::is_const_v<In> or std::is_const_v<std::remove_pointer_t<Out>>)
49{
50 if constexpr (std::is_same_v<std::remove_const_t<In>, remove_cvptr_t<Out>>) {
51 return rhs;
52 } else {
53 return static_cast<Out>(rhs);
54 }
55}
56
59template<typename Out>
60[[nodiscard]] constexpr Out up_cast(nullptr_t) noexcept
61 requires std::is_pointer_v<Out>
62{
63 return nullptr;
64}
65
68template<typename Out, std::derived_from<std::remove_reference_t<Out>> In>
69[[nodiscard]] constexpr Out up_cast(In& rhs) noexcept
70 requires std::is_reference_v<Out> and
71 (std::is_const_v<std::remove_reference_t<Out>> == std::is_const_v<In> or std::is_const_v<std::remove_reference_t<Out>>)
72{
73 if constexpr (std::is_same_v<std::remove_const_t<In>, std::remove_cvref_t<Out>>) {
74 return rhs;
75 } else {
76 return static_cast<Out>(rhs);
77 }
78}
79
87template<typename Out, base_of<std::remove_pointer_t<Out>> In>
88[[nodiscard]] constexpr Out down_cast(In *rhs) noexcept
89 requires std::is_pointer_v<Out> and
90 (std::is_const_v<std::remove_pointer_t<Out>> == std::is_const_v<In> or std::is_const_v<std::remove_pointer_t<Out>>)
91{
92 if constexpr (std::is_same_v<std::remove_const_t<In>, remove_cvptr_t<Out>>) {
93 return rhs;
94 } else {
95 hi_axiom(rhs == nullptr or dynamic_cast<Out>(rhs) != nullptr);
96 return static_cast<Out>(rhs);
97 }
98}
99
104template<typename Out>
105[[nodiscard]] constexpr Out down_cast(nullptr_t) noexcept
106 requires std::is_pointer_v<Out>
107{
108 return nullptr;
109}
110
117template<typename Out, base_of<std::remove_reference_t<Out>> In>
118[[nodiscard]] constexpr Out down_cast(In& rhs) noexcept
119 requires std::is_reference_v<Out> and
120 (std::is_const_v<std::remove_reference_t<Out>> == std::is_const_v<In> or std::is_const_v<std::remove_reference_t<Out>>)
121{
122 if constexpr (std::is_same_v<std::remove_const_t<In>, std::remove_cvref_t<Out>>) {
123 return rhs;
124 } else {
125 hi_axiom(dynamic_cast<std::add_pointer_t<std::remove_reference_t<Out>>>(std::addressof(rhs)) != nullptr);
126 return static_cast<Out>(rhs);
127 }
128}
129
132template<arithmetic Out, arithmetic In>
133[[nodiscard]] constexpr Out wide_cast(In rhs) noexcept
134 requires(type_in_range_v<Out, In>)
135{
136 return static_cast<Out>(rhs);
137}
138
141template<arithmetic Out>
142[[nodiscard]] constexpr Out wide_cast(bool rhs) noexcept
143{
144 return static_cast<Out>(rhs);
145}
146
147namespace detail {
148
149template<arithmetic Out, arithmetic In>
150[[nodiscard]] constexpr bool narrow_validate(Out out, In in) noexcept
151{
152 // in- and out-value compares the same, after converting out-value back to in-type.
153 auto r = (in == static_cast<In>(out));
154
155 // If the types have different signs we need to do an extra test to make sure the actual sign
156 // of the values are the same as well.
158 r &= (in < In{}) == (out < Out{});
159 }
160
161 return r;
162}
163
164} // namespace detail
165
172template<std::integral Out, arithmetic In>
173[[nodiscard]] constexpr Out saturate_cast(In rhs) noexcept
174{
175 if constexpr (std::is_floating_point_v<In>) {
176 if (std::isnan(rhs)) {
177 return Out{0};
178 }
179 }
180
181 if (three_way_compare(rhs, std::numeric_limits<Out>::lowest()) != std::strong_ordering::greater) {
183 } else if (three_way_compare(rhs, std::numeric_limits<Out>::max()) != std::strong_ordering::less) {
185 } else {
186 return static_cast<Out>(rhs);
187 }
188}
189
198template<typename Out, typename In>
199[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept;
200
201template<typename Out, std::same_as<Out> In>
202[[nodiscard]] constexpr Out narrow_cast(In const &rhs) noexcept
203{
204 return rhs;
205}
206
215template<arithmetic Out, arithmetic In>
216[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept requires (not std::same_as<In, Out>)
217{
218 if constexpr (type_in_range_v<Out, In>) {
219 return static_cast<Out>(rhs);
220 } else {
221 hilet r = static_cast<Out>(rhs);
222 hi_axiom(detail::narrow_validate(r, rhs));
223 return r;
224 }
225}
226
227template<arithmetic Out, arithmetic In>
228[[nodiscard]] constexpr Out round_cast(In rhs) noexcept
229{
230 if constexpr (std::is_floating_point_v<In>) {
231 return narrow_cast<Out>(std::round(rhs));
232 } else {
233 return narrow_cast<Out>(rhs);
234 }
235}
236
237template<arithmetic Out, arithmetic In>
238[[nodiscard]] constexpr Out floor_cast(In rhs) noexcept
239{
240 if constexpr (std::is_floating_point_v<In>) {
241 return narrow_cast<Out>(std::floor(rhs));
242 } else {
243 return narrow_cast<Out>(rhs);
244 }
245}
246
247template<arithmetic Out, arithmetic In>
248[[nodiscard]] constexpr Out ceil_cast(In rhs) noexcept
249{
250 if constexpr (std::is_floating_point_v<In>) {
251 return narrow_cast<Out>(std::ceil(rhs));
252 } else {
253 return narrow_cast<Out>(rhs);
254 }
255}
256
259template<std::integral In>
260[[nodiscard]] constexpr std::make_unsigned_t<In> to_unsigned(In rhs) noexcept
261{
262 return static_cast<std::make_unsigned_t<In>>(rhs);
263}
264
267template<std::integral In>
268[[nodiscard]] constexpr std::make_signed_t<In> to_signed(In rhs) noexcept
269{
270 return static_cast<std::make_signed_t<In>>(rhs);
271}
272
275template<std::integral Out, std::integral In>
276[[nodiscard]] constexpr Out truncate(In rhs) noexcept
277{
278 return static_cast<Out>(to_unsigned(rhs));
279}
280
291template<std::integral Out, std::integral In>
292[[nodiscard]] constexpr Out char_cast(In rhs) noexcept
293{
294 using in_unsigned_type = std::make_unsigned_t<In>;
295 using out_unsigned_type = std::make_unsigned_t<Out>;
296
297 // We cast to unsigned of the same type, so that we don't accidentally sign extent 'char'.
298 auto in_unsigned = static_cast<in_unsigned_type>(rhs);
299 auto out_unsigned = narrow_cast<out_unsigned_type>(in_unsigned);
300 return static_cast<Out>(out_unsigned);
301}
302
313template<std::integral Out>
314[[nodiscard]] constexpr Out char_cast(std::byte rhs) noexcept
315{
316 return char_cast<Out>(static_cast<uint8_t>(rhs));
317}
318
321template<std::unsigned_integral OutType, std::unsigned_integral InType>
322[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
323{
324 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of low_bit_cast must be half the size of the input");
325 return static_cast<OutType>(value);
326}
327
330template<std::unsigned_integral OutType, std::unsigned_integral InType>
331[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
332{
333 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of high_bit_cast must be half the size of the input");
334 return static_cast<OutType>(value >> sizeof(OutType) * CHAR_BIT);
335}
336
339template<std::signed_integral OutType, std::signed_integral InType>
340[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
341{
342 using UInType = std::make_unsigned_t<InType>;
343 using UOutType = std::make_unsigned_t<OutType>;
344 return static_cast<OutType>(low_bit_cast<UOutType>(static_cast<UInType>(value)));
345}
346
349template<std::signed_integral OutType, std::signed_integral InType>
350[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
351{
352 using UInType = std::make_unsigned_t<InType>;
353 using UOutType = std::make_unsigned_t<OutType>;
354 return static_cast<OutType>(high_bit_cast<UOutType>(static_cast<UInType>(value)));
355}
356
359template<std::unsigned_integral OutType, std::signed_integral InType>
360[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
361{
362 using UInType = std::make_unsigned_t<InType>;
363 return low_bit_cast<OutType>(static_cast<UInType>(value));
364}
365
368template<std::unsigned_integral OutType, std::signed_integral InType>
369[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
370{
371 using UInType = std::make_unsigned_t<InType>;
372 return high_bit_cast<OutType>(static_cast<UInType>(value));
373}
374
377template<std::unsigned_integral OutType, std::unsigned_integral InType>
378[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
379{
380 static_assert(sizeof(OutType) == sizeof(InType) * 2, "Return value of merge_bit_cast must be double the size of the input");
381
382 OutType r = static_cast<OutType>(hi);
383 r <<= sizeof(InType) * CHAR_BIT;
384 r |= static_cast<OutType>(lo);
385 return r;
386}
387
390template<std::signed_integral OutType, std::signed_integral InType>
391[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
392{
393 using UInType = std::make_unsigned_t<InType>;
394 using UOutType = std::make_unsigned_t<OutType>;
395 return static_cast<OutType>(merge_bit_cast<UOutType>(static_cast<UInType>(hi), static_cast<UInType>(lo)));
396}
397
400template<std::signed_integral OutType, std::unsigned_integral InType>
401[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
402{
403 using UOutType = std::make_unsigned_t<OutType>;
404 return narrow_cast<OutType>(merge_bit_cast<UOutType>(hi, lo));
405}
406
407[[nodiscard]] constexpr auto to_underlying(scoped_enum auto rhs) noexcept
408{
409 return static_cast<std::underlying_type_t<decltype(rhs)>>(rhs);
410}
411
412template<typename T>
413[[nodiscard]] constexpr bool to_bool(T&& rhs) noexcept
414 requires(requires(T&& x) { static_cast<bool>(std::forward<T>(x)); })
415{
416 return static_cast<bool>(std::forward<T>(rhs));
417}
418
419template<typename T>
420[[nodiscard]] inline T to_ptr(std::intptr_t value) noexcept
421 requires std::is_pointer_v<T>
422{
423 return reinterpret_cast<T>(value);
424}
425
426template<typename T>
427[[nodiscard]] std::intptr_t to_int(T *ptr) noexcept
428{
429 return reinterpret_cast<std::intptr_t>(ptr);
430}
431
432template<typename T, byte_like Byte>
433[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(std::span<Byte> bytes)
434{
435 using value_type = copy_cv_t<T, Byte>;
436
437 static_assert(std::is_trivially_default_constructible_v<value_type>);
438 static_assert(std::is_trivially_destructible_v<value_type>);
439
440 if (sizeof(value_type) > bytes.size()) {
441 throw std::bad_cast();
442 }
443 hi_axiom_not_null(bytes.data());
444
445 if constexpr (alignof(value_type) != 1) {
446 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
447 throw std::bad_cast();
448 }
449 }
450
451 return *reinterpret_cast<value_type *>(bytes.data());
452}
453
454template<typename T, byte_like Byte>
455[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(std::span<Byte> bytes, size_t n)
456{
457 using value_type = copy_cv_t<T, Byte>;
458
459 static_assert(std::is_trivially_default_constructible_v<value_type>);
460 static_assert(std::is_trivially_destructible_v<value_type>);
461
462 if (sizeof(value_type) * n > bytes.size()) {
463 throw std::bad_cast();
464 }
465 hi_axiom_not_null(bytes.data());
466
467 if constexpr (alignof(value_type) != 1) {
468 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
469 throw std::bad_cast();
470 }
471 }
472
473 return {reinterpret_cast<value_type *>(bytes.data()), n};
474}
475
476template<typename T, byte_like Byte>
477[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(size_t& offset, std::span<Byte> bytes)
478{
479 using value_type = copy_cv_t<T, Byte>;
480
481 static_assert(std::is_trivially_default_constructible_v<value_type>);
482 static_assert(std::is_trivially_destructible_v<value_type>);
483
484 if (sizeof(value_type) + offset > bytes.size()) {
485 throw std::bad_cast();
486 }
487 hi_axiom_not_null(bytes.data());
488
489 hilet data = bytes.data() + offset;
490
491 if constexpr (alignof(value_type) != 1) {
492 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
493 throw std::bad_cast();
494 }
495 }
496
497 offset += sizeof(value_type);
498 return *reinterpret_cast<value_type *>(data);
499}
500
501template<typename T, byte_like Byte>
502[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(size_t& offset, std::span<Byte> bytes, size_t n)
503{
504 using value_type = copy_cv_t<T, Byte>;
505
506 static_assert(std::is_trivially_default_constructible_v<value_type>);
507 static_assert(std::is_trivially_destructible_v<value_type>);
508
509 if (sizeof(value_type) * n + offset > bytes.size()) {
510 throw std::bad_cast();
511 }
512 hi_axiom_not_null(bytes.data());
513
514 hilet data = bytes.data() + offset;
515
516 if constexpr (alignof(value_type) != 1) {
517 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
518 throw std::bad_cast();
519 }
520 }
521
522 offset += sizeof(value_type) * n;
523 return {reinterpret_cast<value_type *>(data), n};
524}
525
526} // namespace hi::inline v1
527
528hi_warning_pop();
Utilities to assert and bound check.
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:253
#define hi_axiom_not_null(expression,...)
Assert if an expression is not nullptr.
Definition assert.hpp:272
Safe numeric comparison between different types.
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:13
constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
Return the upper half of the input value.
Definition cast.hpp:378
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:133
constexpr OutType high_bit_cast(InType value) noexcept
Return the upper half of the input value.
Definition cast.hpp:331
constexpr Out saturate_cast(In rhs) noexcept
Cast a numeric value to an integer saturating on overflow.
Definition cast.hpp:173
constexpr Out up_cast(In *rhs) noexcept
Cast a pointer to a class to its base class or itself.
Definition cast.hpp:46
constexpr Out char_cast(In rhs) noexcept
Cast a character.
Definition cast.hpp:292
constexpr std::make_unsigned_t< In > to_unsigned(In rhs) noexcept
Cast an integral to an unsigned integral of the same size.
Definition cast.hpp:260
constexpr OutType low_bit_cast(InType value) noexcept
Return the low half of the input value.
Definition cast.hpp:322
constexpr Out down_cast(In *rhs) noexcept
Cast a pointer to a class to its derived class or itself.
Definition cast.hpp:88
constexpr Out truncate(In rhs) noexcept
Cast between integral types truncating or zero-extending the result.
Definition cast.hpp:276
constexpr std::make_signed_t< In > to_signed(In rhs) noexcept
Cast an integral to an signed integral of the same size.
Definition cast.hpp:268
geometry/margins.hpp
Definition cache.hpp:11
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)