HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
cast.hpp
Go to the documentation of this file.
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
9#pragma once
10
11#include "utility.hpp"
12#include "type_traits.hpp"
13#include "concepts.hpp"
14#include "assert.hpp"
15#include "compare.hpp"
16#include <type_traits>
17#include <concepts>
18#include <climits>
19#include <span>
20
21hi_warning_push();
22// C26472: Don't use static_cast for arithmetic conversions, Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
23// This file implements narrow_cast().
25// C26467: Converting from floating point to unsigned integral types results in non-portable code if the double/float has
26// a negative value. Use gsl::narrow_cast or gsl::naroow instead to guard against undefined behavior and potential data loss
27// (es.46).
28// This file implements narrow_cast().
30// C26496: The variable 'r' does not change after construction, mark it as const (con.4).
31// False positive
33// C26466: Don't use static_cast downcast. A cast from a polymorphic type should use dynamic_cast (type.2)
34// Used in down_cast<>() specifically for doing this savely.
36// C26474: Don't cast between pointer types when the conversion could be implicit (type.1).
37// Since these functions are templates this happens.
39
40namespace hi::inline v1 {
41template<typename T>
42[[nodiscard]] constexpr T copy(T value) noexcept
43{
44 return value;
45}
46
54template<typename Out, typename In>
55[[nodiscard]] constexpr Out up_cast(In *rhs) noexcept
56{
57 using out_type = std::remove_pointer_t<Out>;
58
59 static_assert(std::is_pointer_v<Out>, "up_cast() Out template paramater must be a pointer if the input is a pointer.");
60 static_assert(std::is_const_v<out_type> == std::is_const_v<In> or std::is_const_v<out_type>, "up_cast() can not cast away const.");
61 static_assert(std::is_base_of_v<out_type, In>, "up_cast() may only be used to cast to a base-type.");
62
63 return static_cast<Out>(rhs);
64}
65
73template<typename Out>
74[[nodiscard]] constexpr Out up_cast(nullptr_t) noexcept
75{
76 static_assert(std::is_pointer_v<Out>, "up_cast() Out template paramater must be a pointer.");
77 return nullptr;
78}
79
87template<typename Out, typename In>
88[[nodiscard]] constexpr Out up_cast(In& rhs) noexcept
89{
90 using out_type = std::remove_reference_t<Out>;
91
92 static_assert(std::is_reference_v<Out>, "up_cast() Out template paramater must be a reference if the input is a reference.");
93 static_assert(std::is_const_v<out_type> == std::is_const_v<In> or std::is_const_v<out_type>, "up_cast() can not cast away const.");
94 static_assert(std::is_base_of_v<out_type, In>, "up_cast() may only be used to cast to a base-type.");
95
96 return static_cast<Out>(rhs);
97}
98
107template<typename Out, typename In>
108[[nodiscard]] constexpr Out down_cast(In *rhs) noexcept
109{
110 using out_type = std::remove_pointer_t<Out>;
111
112 static_assert(std::is_pointer_v<Out>, "down_cast() Out template paramater must be a pointer if the input is a pointer.");
113 static_assert(std::is_const_v<out_type> == std::is_const_v<In> or std::is_const_v<out_type>, "down_cast() can not cast away const.");
114 static_assert(std::is_base_of_v<out_type, In> or std::is_base_of_v<In, out_type>, "down_cast() may only be used to cast to a related type.");
115
116 if constexpr (not std::is_base_of_v<out_type, In>) {
117 hi_axiom(rhs == nullptr or dynamic_cast<Out>(rhs) != nullptr);
118 }
119 return static_cast<Out>(rhs);
120}
121
127template<typename Out>
128[[nodiscard]] constexpr Out down_cast(nullptr_t) noexcept
129 requires std::is_pointer_v<Out>
130{
131 return nullptr;
132}
133
141template<typename Out, typename In>
142[[nodiscard]] constexpr Out down_cast(In& rhs) noexcept
143{
144 using out_type = std::remove_reference_t<Out>;
145
146 static_assert(std::is_reference_v<Out>, "down_cast() Out template paramater must be a reference if the input is a reference.");
147 static_assert(std::is_const_v<out_type> == std::is_const_v<In> or std::is_const_v<out_type>, "down_cast() can not cast away const.");
148 static_assert(std::is_base_of_v<out_type, In> or std::is_base_of_v<In, out_type>, "down_cast() may only be used to cast to a related type.");
149
150 if constexpr (not std::is_base_of_v<out_type, In>) {
151 hi_axiom(dynamic_cast<std::add_pointer_t<out_type>>(std::addressof(rhs)) != nullptr);
152 }
153 return static_cast<Out>(rhs);
154}
155
164template<typename Out, std::same_as<Out> In>
165[[nodiscard]] constexpr Out wide_cast(In const& rhs) noexcept
166{
167 return rhs;
168}
169
177template<std::floating_point Out, std::floating_point In>
178[[nodiscard]] constexpr Out wide_cast(In const& rhs) noexcept
179 requires(not std::same_as<In, Out>)
180{
181 static_assert(
183 "wide_cast() is only allowed to a floating point of the same or larger size.");
184
185 return static_cast<Out>(rhs);
186}
187
195template<std::integral Out, std::integral In>
196[[nodiscard]] constexpr Out wide_cast(In rhs) noexcept
197 requires(not std::same_as<In, Out>)
198{
199 static_assert(
201 "wide_cast() is only allowed if the input is unsigned or if both input and output have the same signess.");
202
203 static_assert(
205 "wide_cast() is only allowed to an integer of the same or larger size.");
206
207 return static_cast<Out>(rhs);
208}
209
224template<std::floating_point Out, std::integral In>
225[[nodiscard]] constexpr Out wide_cast(In rhs) noexcept
226{
227 static_assert(
229 "wide_cast() is only allowed if the input can be represented with perfect accuracy by the floating point output type.");
230
231 return static_cast<Out>(rhs);
232}
233
240template<std::integral Out, arithmetic In>
241[[nodiscard]] constexpr Out saturate_cast(In rhs) noexcept
242{
243 if constexpr (std::is_floating_point_v<In>) {
244 if (std::isnan(rhs)) {
245 return Out{0};
246 }
247 }
248
249 if (three_way_compare(rhs, std::numeric_limits<Out>::lowest()) != std::strong_ordering::greater) {
251 } else if (three_way_compare(rhs, std::numeric_limits<Out>::max()) != std::strong_ordering::less) {
253 } else {
254 return static_cast<Out>(rhs);
255 }
256}
257
265template<typename Out, std::same_as<Out> In>
266[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
267{
268 return true;
269}
270
278template<std::floating_point Out, std::floating_point In>
279[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
280 requires(not std::same_as<In, Out>)
281{
283 // cast is allowed when the input is NaN, infinite or within the range of the output type.
284 return rhs != rhs or rhs == std::numeric_limits<In>::infinity() or rhs == -std::numeric_limits<In>::infinity() or
286 }
287
288 return true;
289}
290
298template<std::integral Out, std::integral In>
299[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
300 requires(not std::same_as<In, Out>)
301{
305 if (rhs < static_cast<In>(std::numeric_limits<Out>::lowest())) {
306 return false;
307 }
308 }
309 return rhs <= static_cast<In>(std::numeric_limits<Out>::max());
310 }
311
312 } else if constexpr (std::numeric_limits<In>::is_signed) {
313 if (rhs < 0) {
314 return false;
315 }
316
318 return rhs <= static_cast<In>(std::numeric_limits<Out>::max());
319 }
320
321 } else {
323 return rhs <= static_cast<In>(std::numeric_limits<Out>::max());
324 }
325 }
326
327 return true;
328}
329
337template<std::floating_point Out, std::integral In>
338[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
339{
342 constexpr auto max = (1LL << std::numeric_limits<Out>::digits) - 1;
343 constexpr auto lowest = -max;
344
345 return rhs >= lowest and rhs <= max;
346
347 } else {
348 constexpr auto max = (1ULL << std::numeric_limits<Out>::digits) - 1;
349
350 return rhs <= max;
351 }
352 }
353
354 return true;
355}
356
366template<typename Out, std::same_as<Out> In>
367[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
368{
369 return rhs;
370}
371
381template<std::floating_point Out, std::floating_point In>
382[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
383 requires(not std::same_as<In, Out>)
384{
386 // cast is allowed when the input is NaN, infinite or within the range of the output type.
387 hi_axiom(
388 rhs != rhs or rhs == std::numeric_limits<In>::infinity() or rhs == -std::numeric_limits<In>::infinity() or
390 }
391
392 return static_cast<Out>(rhs);
393}
394
404template<std::integral Out, std::integral In>
405[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
406 requires(not std::same_as<In, Out>)
407{
411 hi_axiom(rhs >= static_cast<In>(std::numeric_limits<Out>::lowest()));
412 }
413 hi_axiom(rhs <= static_cast<In>(std::numeric_limits<Out>::max()));
414 }
415
416 } else if constexpr (std::numeric_limits<In>::is_signed) {
417 hi_axiom(rhs >= 0);
419 hi_axiom(rhs <= static_cast<In>(std::numeric_limits<Out>::max()));
420 }
421
422 } else {
424 hi_axiom(rhs <= static_cast<In>(std::numeric_limits<Out>::max()));
425 }
426 }
427
428 return static_cast<Out>(rhs);
429}
430
440template<std::floating_point Out, std::integral In>
441[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
442{
445 constexpr auto max = (1LL << std::numeric_limits<Out>::digits) - 1;
446 constexpr auto lowest = -max;
447
448 hi_axiom(rhs >= lowest and rhs <= max);
449
450 } else {
451 constexpr auto max = (1ULL << std::numeric_limits<Out>::digits) - 1;
452
453 hi_axiom(rhs <= max);
454 }
455 }
456
457 return static_cast<Out>(rhs);
458}
459
460template<std::integral Out, std::floating_point In>
461[[nodiscard]] constexpr bool can_round_cast(In rhs) noexcept
462{
463 hilet rhs_ = std::round(rhs);
464 return rhs_ >= std::numeric_limits<Out>::lowest() and rhs_ <= std::numeric_limits<Out>::max();
465}
466
467template<std::integral Out, std::floating_point In>
468[[nodiscard]] constexpr bool can_floor_cast(In rhs) noexcept
469{
470 hilet rhs_ = std::floor(rhs);
471 return rhs_ >= std::numeric_limits<Out>::lowest() and rhs_ <= std::numeric_limits<Out>::max();
472}
473
474template<std::integral Out, std::floating_point In>
475[[nodiscard]] constexpr bool can_ceil_cast(In rhs) noexcept
476{
477 hilet rhs_ = std::ceil(rhs);
478 return rhs_ >= std::numeric_limits<Out>::lowest() and rhs_ <= std::numeric_limits<Out>::max();
479}
480
481template<std::integral Out, std::floating_point In>
482[[nodiscard]] constexpr Out round_cast(In rhs) noexcept
483{
484 hilet rhs_ = std::round(rhs);
486 return static_cast<Out>(rhs_);
487}
488
489template<std::integral Out, std::floating_point In>
490[[nodiscard]] constexpr Out floor_cast(In rhs) noexcept
491{
492 hilet rhs_ = std::floor(rhs);
494 return static_cast<Out>(rhs_);
495}
496
497template<std::integral Out, std::floating_point In>
498[[nodiscard]] constexpr Out ceil_cast(In rhs) noexcept
499{
500 hilet rhs_ = std::ceil(rhs);
502 return static_cast<Out>(rhs_);
503}
504
507template<std::integral In>
508[[nodiscard]] constexpr std::make_unsigned_t<In> to_unsigned(In rhs) noexcept
509{
510 return static_cast<std::make_unsigned_t<In>>(rhs);
511}
512
515template<std::integral In>
516[[nodiscard]] constexpr std::make_signed_t<In> to_signed(In rhs) noexcept
517{
518 return static_cast<std::make_signed_t<In>>(rhs);
519}
520
523template<std::integral Out, std::integral In>
524[[nodiscard]] constexpr Out truncate(In rhs) noexcept
525{
526 return static_cast<Out>(to_unsigned(rhs));
527}
528
539template<std::integral Out, std::integral In>
540[[nodiscard]] constexpr Out char_cast(In rhs) noexcept
541{
542 using in_unsigned_type = std::make_unsigned_t<In>;
543 using out_unsigned_type = std::make_unsigned_t<Out>;
544
545 // We cast to unsigned of the same type, so that we don't accidentally sign extent 'char'.
546 auto in_unsigned = static_cast<in_unsigned_type>(rhs);
547 auto out_unsigned = narrow_cast<out_unsigned_type>(in_unsigned);
548 return static_cast<Out>(out_unsigned);
549}
550
561template<std::integral Out>
562[[nodiscard]] constexpr Out char_cast(std::byte rhs) noexcept
563{
564 return char_cast<Out>(static_cast<uint8_t>(rhs));
565}
566
569template<std::unsigned_integral OutType, std::unsigned_integral InType>
570[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
571{
572 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of low_bit_cast must be half the size of the input");
573 return static_cast<OutType>(value);
574}
575
578template<std::unsigned_integral OutType, std::unsigned_integral InType>
579[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
580{
581 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of high_bit_cast must be half the size of the input");
582 return static_cast<OutType>(value >> sizeof(OutType) * CHAR_BIT);
583}
584
587template<std::signed_integral OutType, std::signed_integral InType>
588[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
589{
590 using UInType = std::make_unsigned_t<InType>;
591 using UOutType = std::make_unsigned_t<OutType>;
592 return static_cast<OutType>(low_bit_cast<UOutType>(static_cast<UInType>(value)));
593}
594
597template<std::signed_integral OutType, std::signed_integral InType>
598[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
599{
600 using UInType = std::make_unsigned_t<InType>;
601 using UOutType = std::make_unsigned_t<OutType>;
602 return static_cast<OutType>(high_bit_cast<UOutType>(static_cast<UInType>(value)));
603}
604
607template<std::unsigned_integral OutType, std::signed_integral InType>
608[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
609{
610 using UInType = std::make_unsigned_t<InType>;
611 return low_bit_cast<OutType>(static_cast<UInType>(value));
612}
613
616template<std::unsigned_integral OutType, std::signed_integral InType>
617[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
618{
619 using UInType = std::make_unsigned_t<InType>;
620 return high_bit_cast<OutType>(static_cast<UInType>(value));
621}
622
625template<std::unsigned_integral OutType, std::unsigned_integral InType>
626[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
627{
628 static_assert(sizeof(OutType) == sizeof(InType) * 2, "Return value of merge_bit_cast must be double the size of the input");
629
630 OutType r = static_cast<OutType>(hi);
631 r <<= sizeof(InType) * CHAR_BIT;
632 r |= static_cast<OutType>(lo);
633 return r;
634}
635
638template<std::signed_integral OutType, std::signed_integral InType>
639[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
640{
641 using UInType = std::make_unsigned_t<InType>;
642 using UOutType = std::make_unsigned_t<OutType>;
643 return static_cast<OutType>(merge_bit_cast<UOutType>(static_cast<UInType>(hi), static_cast<UInType>(lo)));
644}
645
648template<std::signed_integral OutType, std::unsigned_integral InType>
649[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
650{
651 using UOutType = std::make_unsigned_t<OutType>;
652 return narrow_cast<OutType>(merge_bit_cast<UOutType>(hi, lo));
653}
654
655[[nodiscard]] constexpr auto to_underlying(scoped_enum auto rhs) noexcept
656{
657 return static_cast<std::underlying_type_t<decltype(rhs)>>(rhs);
658}
659
660template<typename T>
661[[nodiscard]] constexpr bool to_bool(T&& rhs) noexcept
662 requires(requires(T&& x) { static_cast<bool>(std::forward<T>(x)); })
663{
664 return static_cast<bool>(std::forward<T>(rhs));
665}
666
667template<typename T>
668[[nodiscard]] inline T to_ptr(std::intptr_t value) noexcept
669 requires std::is_pointer_v<T>
670{
671 return reinterpret_cast<T>(value);
672}
673
674template<typename T>
675[[nodiscard]] std::intptr_t to_int(T *ptr) noexcept
676{
677 return reinterpret_cast<std::intptr_t>(ptr);
678}
679
680template<typename T, byte_like Byte>
681[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(std::span<Byte> bytes)
682{
683 using value_type = copy_cv_t<T, Byte>;
684
685 static_assert(std::is_trivially_default_constructible_v<value_type>);
686 static_assert(std::is_trivially_destructible_v<value_type>);
687
688 if (sizeof(value_type) > bytes.size()) {
689 throw std::bad_cast();
690 }
691 hi_axiom_not_null(bytes.data());
692
693 if constexpr (alignof(value_type) != 1) {
694 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
695 throw std::bad_cast();
696 }
697 }
698
699 return *reinterpret_cast<value_type *>(bytes.data());
700}
701
702template<typename T, byte_like Byte>
703[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(std::span<Byte> bytes, size_t n)
704{
705 using value_type = copy_cv_t<T, Byte>;
706
707 static_assert(std::is_trivially_default_constructible_v<value_type>);
708 static_assert(std::is_trivially_destructible_v<value_type>);
709
710 if (sizeof(value_type) * n > bytes.size()) {
711 throw std::bad_cast();
712 }
713 hi_axiom_not_null(bytes.data());
714
715 if constexpr (alignof(value_type) != 1) {
716 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
717 throw std::bad_cast();
718 }
719 }
720
721 return {reinterpret_cast<value_type *>(bytes.data()), n};
722}
723
724template<typename T, byte_like Byte>
725[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(size_t& offset, std::span<Byte> bytes)
726{
727 using value_type = copy_cv_t<T, Byte>;
728
729 static_assert(std::is_trivially_default_constructible_v<value_type>);
730 static_assert(std::is_trivially_destructible_v<value_type>);
731
732 if (sizeof(value_type) + offset > bytes.size()) {
733 throw std::bad_cast();
734 }
735 hi_axiom_not_null(bytes.data());
736
737 hilet data = bytes.data() + offset;
738
739 if constexpr (alignof(value_type) != 1) {
740 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
741 throw std::bad_cast();
742 }
743 }
744
745 offset += sizeof(value_type);
746 return *reinterpret_cast<value_type *>(data);
747}
748
749template<typename T, byte_like Byte>
750[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(size_t& offset, std::span<Byte> bytes, size_t n)
751{
752 using value_type = copy_cv_t<T, Byte>;
753
754 static_assert(std::is_trivially_default_constructible_v<value_type>);
755 static_assert(std::is_trivially_destructible_v<value_type>);
756
757 if (sizeof(value_type) * n + offset > bytes.size()) {
758 throw std::bad_cast();
759 }
760 hi_axiom_not_null(bytes.data());
761
762 hilet data = bytes.data() + offset;
763
764 if constexpr (alignof(value_type) != 1) {
765 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
766 throw std::bad_cast();
767 }
768 }
769
770 offset += sizeof(value_type) * n;
771 return {reinterpret_cast<value_type *>(data), n};
772}
773
774} // namespace hi::inline v1
775
776hi_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
hi_warning_ignore_msvc(26472)
Definition int_carry.hpp:27
STL namespace.
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:626
constexpr OutType high_bit_cast(InType value) noexcept
Return the upper half of the input value.
Definition cast.hpp:579
constexpr Out saturate_cast(In rhs) noexcept
Cast a numeric value to an integer saturating on overflow.
Definition cast.hpp:241
constexpr Out char_cast(In rhs) noexcept
Cast a character.
Definition cast.hpp:540
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:367
constexpr Out down_cast(In *rhs) noexcept
Cast a pointer to a class to its derived class or itself.
Definition cast.hpp:108
constexpr Out up_cast(In *rhs) noexcept
Cast a pointer to a class to its base class or itself.
Definition cast.hpp:55
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:508
constexpr OutType low_bit_cast(InType value) noexcept
Return the low half of the input value.
Definition cast.hpp:570
constexpr bool can_narrow_cast(In const &rhs) noexcept
Check if a value can be casted to a narrow type.
Definition cast.hpp:266
constexpr Out truncate(In rhs) noexcept
Cast between integral types truncating or zero-extending the result.
Definition cast.hpp:524
constexpr Out wide_cast(In const &rhs) noexcept
Cast to a type which can hold all values from the input type.
Definition cast.hpp:165
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:516
geometry/margins.hpp
Definition cache.hpp:11
T addressof(T... args)
T ceil(T... args)
T copy(T... args)
T floor(T... args)
T infinity(T... args)
T isnan(T... args)
T lowest(T... args)
T max(T... args)
T round(T... args)