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 "type_traits.hpp"
12#include "../macros.hpp"
13#include "assert.hpp"
14#include "compare.hpp"
15#include "concepts.hpp"
16#include "debugger.hpp"
17#include "exception.hpp"
18#include <type_traits>
19#include <concepts>
20#include <limits>
21#include <span>
22#include <numeric>
23#include <bit>
24#include <cmath>
25
26hi_export_module(hikogui.utility.cast);
27
28hi_warning_push();
29// C26472: Don't use static_cast for arithmetic conversions, Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
30// This file implements narrow_cast().
31hi_warning_ignore_msvc(26472);
32// C26467: Converting from floating point to unsigned integral types results in non-portable code if the double/float has
33// a negative value. Use gsl::narrow_cast or gsl::naroow instead to guard against undefined behavior and potential data loss
34// (es.46).
35// This file implements narrow_cast().
36hi_warning_ignore_msvc(26467);
37// C26496: The variable 'r' does not change after construction, mark it as const (con.4).
38// False positive
39hi_warning_ignore_msvc(26496);
40// C26466: Don't use static_cast downcast. A cast from a polymorphic type should use dynamic_cast (type.2)
41// Used in down_cast<>() specifically for doing this savely.
42hi_warning_ignore_msvc(26466);
43// C26474: Don't cast between pointer types when the conversion could be implicit (type.1).
44// Since these functions are templates this happens.
45hi_warning_ignore_msvc(26474);
46// C4702 unreachable code: Suppressed due intrinsics and std::is_constant_evaluated()
47hi_warning_ignore_msvc(4702);
48
49hi_export namespace hi { inline namespace v1 {
50
51template<typename T>
52[[nodiscard]] constexpr T copy(T value) noexcept
53{
54 return value;
55}
56
64template<typename Out, typename In>
65[[nodiscard]] constexpr Out up_cast(In *rhs) noexcept
66{
67 using out_type = std::remove_pointer_t<Out>;
68
69 static_assert(std::is_pointer_v<Out>, "up_cast() Out template paramater must be a pointer if the input is a pointer.");
70 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.");
71 static_assert(std::is_base_of_v<out_type, In>, "up_cast() may only be used to cast to a base-type.");
72
73 return static_cast<Out>(rhs);
74}
75
83template<typename Out>
84[[nodiscard]] constexpr Out up_cast(nullptr_t) noexcept
85{
86 static_assert(std::is_pointer_v<Out>, "up_cast() Out template paramater must be a pointer.");
87 return nullptr;
88}
89
97template<typename Out, typename In>
98[[nodiscard]] constexpr Out up_cast(In& rhs) noexcept
99{
100 using out_type = std::remove_reference_t<Out>;
101
102 static_assert(std::is_reference_v<Out>, "up_cast() Out template paramater must be a reference if the input is a reference.");
103 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.");
104 static_assert(std::is_base_of_v<out_type, In>, "up_cast() may only be used to cast to a base-type.");
105
106 return static_cast<Out>(rhs);
107}
108
117template<typename Out, typename In>
118[[nodiscard]] constexpr Out down_cast(In *rhs) noexcept
119{
120 using out_type = std::remove_pointer_t<Out>;
121
122 static_assert(std::is_pointer_v<Out>, "down_cast() Out template paramater must be a pointer if the input is a pointer.");
123 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.");
124 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.");
125
126 if constexpr (not std::is_base_of_v<out_type, In>) {
127 hi_axiom(rhs == nullptr or dynamic_cast<Out>(rhs) != nullptr);
128 }
129 return static_cast<Out>(rhs);
130}
131
137template<typename Out>
138[[nodiscard]] constexpr Out down_cast(nullptr_t) noexcept
139 requires std::is_pointer_v<Out>
140{
141 return nullptr;
142}
143
151template<typename Out, typename In>
152[[nodiscard]] constexpr Out down_cast(In& rhs) noexcept
153{
154 using out_type = std::remove_reference_t<Out>;
155
156 static_assert(std::is_reference_v<Out>, "down_cast() Out template paramater must be a reference if the input is a reference.");
157 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.");
158 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.");
159
160 if constexpr (not std::is_base_of_v<out_type, In>) {
161 hi_axiom(dynamic_cast<std::add_pointer_t<out_type>>(std::addressof(rhs)) != nullptr);
162 }
163 return static_cast<Out>(rhs);
164}
165
174template<typename Out, std::same_as<Out> In>
175[[nodiscard]] constexpr Out wide_cast(In const& rhs) noexcept
176{
177 return rhs;
178}
179
187template<std::floating_point Out, std::floating_point In>
188[[nodiscard]] constexpr Out wide_cast(In const& rhs) noexcept
189 requires(not std::same_as<In, Out>)
190{
191 static_assert(
193 "wide_cast() is only allowed to a floating point of the same or larger size.");
194
195 return static_cast<Out>(rhs);
196}
197
205template<std::integral Out, std::integral In>
206[[nodiscard]] constexpr Out wide_cast(In rhs) noexcept
207 requires(not std::same_as<In, Out>)
208{
209 static_assert(
211 "wide_cast() is only allowed if the input is unsigned or if both input and output have the same signess.");
212
213 static_assert(
215 "wide_cast() is only allowed to an integer of the same or larger size.");
216
217 return static_cast<Out>(rhs);
218}
219
234template<std::floating_point Out, std::integral In>
235[[nodiscard]] constexpr Out wide_cast(In rhs) noexcept
236{
237 static_assert(
239 "wide_cast() is only allowed if the input can be represented with perfect accuracy by the floating point output type.");
240
241 return static_cast<Out>(rhs);
242}
243
250template<std::integral Out, arithmetic In>
251[[nodiscard]] constexpr Out saturate_cast(In rhs) noexcept
252{
253 if constexpr (std::is_floating_point_v<In>) {
254 if (std::isnan(rhs)) {
255 return Out{0};
256 }
257 }
258
259 if (three_way_compare(rhs, std::numeric_limits<Out>::lowest()) != std::strong_ordering::greater) {
261 } else if (three_way_compare(rhs, std::numeric_limits<Out>::max()) != std::strong_ordering::less) {
263 } else {
264 return static_cast<Out>(rhs);
265 }
266}
267
275template<typename Out, std::same_as<Out> In>
276[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
277{
278 return true;
279}
280
288template<std::floating_point Out, std::floating_point In>
289[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
290 requires(not std::same_as<In, Out>)
291{
293 // cast is allowed when the input is NaN, infinite or within the range of the output type.
296 }
297
298 return true;
299}
300
308template<std::integral Out, std::integral In>
309[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
310 requires(not std::same_as<In, Out>)
311{
315 if (rhs < static_cast<In>(std::numeric_limits<Out>::lowest())) {
316 return false;
317 }
318 }
320 }
321
322 } else if constexpr (std::numeric_limits<In>::is_signed) {
323 if (rhs < 0) {
324 return false;
325 }
326
329 }
330
331 } else {
334 }
335 }
336
337 return true;
338}
339
347template<std::floating_point Out, std::integral In>
348[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
349{
352 constexpr auto max = (1LL << std::numeric_limits<Out>::digits) - 1;
353 constexpr auto lowest = -max;
354
355 return rhs >= lowest and rhs <= max;
356
357 } else {
358 constexpr auto max = (1ULL << std::numeric_limits<Out>::digits) - 1;
359
360 return rhs <= max;
361 }
362 }
363
364 return true;
365}
366
376template<typename Out, std::same_as<Out> In>
377[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
378{
379 return rhs;
380}
381
391template<std::floating_point Out, std::floating_point In>
392[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
393 requires(not std::same_as<In, Out>)
394{
396 // cast is allowed when the input is NaN, infinite or within the range of the output type.
397 hi_axiom(
400 }
401
402 return static_cast<Out>(rhs);
403}
404
414template<std::integral Out, std::integral In>
415[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
416 requires(not std::same_as<In, Out>)
417{
421 hi_axiom(rhs >= static_cast<In>(std::numeric_limits<Out>::lowest()));
422 }
423 hi_axiom(rhs <= static_cast<In>(std::numeric_limits<Out>::max()));
424 }
425
426 } else if constexpr (std::numeric_limits<In>::is_signed) {
427 hi_axiom(rhs >= 0);
429 hi_axiom(rhs <= static_cast<In>(std::numeric_limits<Out>::max()));
430 }
431
432 } else {
434 hi_axiom(rhs <= static_cast<In>(std::numeric_limits<Out>::max()));
435 }
436 }
437
438 return static_cast<Out>(rhs);
439}
440
450template<std::floating_point Out, std::integral In>
451[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
452{
455 constexpr auto max = (1LL << std::numeric_limits<Out>::digits) - 1;
456 constexpr auto lowest = -max;
457
458 hi_axiom(rhs >= lowest and rhs <= max);
459
460 } else {
461 constexpr auto max = (1ULL << std::numeric_limits<Out>::digits) - 1;
462
463 hi_axiom(rhs <= max);
464 }
465 }
466
467 return static_cast<Out>(rhs);
468}
469
470template<std::integral Out, std::floating_point In>
471[[nodiscard]] constexpr bool can_round_cast(In rhs) noexcept
472{
473 hilet rhs_ = std::round(rhs);
474 return rhs_ >= std::numeric_limits<Out>::lowest() and rhs_ <= std::numeric_limits<Out>::max();
475}
476
477template<std::integral Out, std::floating_point In>
478[[nodiscard]] constexpr bool can_floor_cast(In rhs) noexcept
479{
480 hilet rhs_ = std::floor(rhs);
481 return rhs_ >= std::numeric_limits<Out>::lowest() and rhs_ <= std::numeric_limits<Out>::max();
482}
483
484template<std::integral Out, std::floating_point In>
485[[nodiscard]] constexpr bool can_ceil_cast(In rhs) noexcept
486{
487 hilet rhs_ = std::ceil(rhs);
488 return rhs_ >= std::numeric_limits<Out>::lowest() and rhs_ <= std::numeric_limits<Out>::max();
489}
490
491template<std::integral Out, std::floating_point In>
492[[nodiscard]] constexpr Out round_cast(In rhs) noexcept
493{
494 hilet rhs_ = std::round(rhs);
496 return static_cast<Out>(rhs_);
497}
498
499template<std::integral Out, std::floating_point In>
500[[nodiscard]] constexpr Out floor_cast(In rhs) noexcept
501{
502 hilet rhs_ = std::floor(rhs);
504 return static_cast<Out>(rhs_);
505}
506
507template<std::integral Out, std::floating_point In>
508[[nodiscard]] constexpr Out ceil_cast(In rhs) noexcept
509{
510 hilet rhs_ = std::ceil(rhs);
512 return static_cast<Out>(rhs_);
513}
514
517template<std::integral In>
518[[nodiscard]] constexpr std::make_unsigned_t<In> to_unsigned(In rhs) noexcept
519{
520 return static_cast<std::make_unsigned_t<In>>(rhs);
521}
522
525template<std::integral In>
526[[nodiscard]] constexpr std::make_signed_t<In> to_signed(In rhs) noexcept
527{
528 return static_cast<std::make_signed_t<In>>(rhs);
529}
530
533template<std::integral Out, std::integral In>
534[[nodiscard]] constexpr Out truncate(In rhs) noexcept
535{
536 return static_cast<Out>(to_unsigned(rhs));
537}
538
549template<std::integral Out, std::integral In>
550[[nodiscard]] constexpr Out char_cast(In rhs) noexcept
551{
552 using in_unsigned_type = std::make_unsigned_t<In>;
553 using out_unsigned_type = std::make_unsigned_t<Out>;
554
555 // We cast to unsigned of the same type, so that we don't accidentally sign extent 'char'.
556 auto in_unsigned = static_cast<in_unsigned_type>(rhs);
558 return static_cast<Out>(out_unsigned);
559}
560
571template<std::integral Out>
572[[nodiscard]] constexpr Out char_cast(std::byte rhs) noexcept
573{
574 return char_cast<Out>(static_cast<uint8_t>(rhs));
575}
576
579template<std::unsigned_integral OutType, std::unsigned_integral InType>
580[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
581{
582 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of low_bit_cast must be half the size of the input");
583 return static_cast<OutType>(value);
584}
585
588template<std::unsigned_integral OutType, std::unsigned_integral InType>
589[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
590{
591 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of high_bit_cast must be half the size of the input");
592 return static_cast<OutType>(value >> sizeof(OutType) * CHAR_BIT);
593}
594
597template<std::signed_integral OutType, std::signed_integral InType>
598[[nodiscard]] constexpr OutType low_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>(low_bit_cast<UOutType>(static_cast<UInType>(value)));
603}
604
607template<std::signed_integral OutType, std::signed_integral InType>
608[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
609{
610 using UInType = std::make_unsigned_t<InType>;
611 using UOutType = std::make_unsigned_t<OutType>;
612 return static_cast<OutType>(high_bit_cast<UOutType>(static_cast<UInType>(value)));
613}
614
617template<std::unsigned_integral OutType, std::signed_integral InType>
618[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
619{
620 using UInType = std::make_unsigned_t<InType>;
621 return low_bit_cast<OutType>(static_cast<UInType>(value));
622}
623
626template<std::unsigned_integral OutType, std::signed_integral InType>
627[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
628{
629 using UInType = std::make_unsigned_t<InType>;
630 return high_bit_cast<OutType>(static_cast<UInType>(value));
631}
632
635template<std::unsigned_integral OutType, std::unsigned_integral InType>
637{
638 static_assert(sizeof(OutType) == sizeof(InType) * 2, "Return value of merge_bit_cast must be double the size of the input");
639
640 OutType r = static_cast<OutType>(hi);
641 r <<= sizeof(InType) * CHAR_BIT;
642 r |= static_cast<OutType>(lo);
643 return r;
644}
645
648template<std::signed_integral OutType, std::signed_integral InType>
649[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
650{
651 using UInType = std::make_unsigned_t<InType>;
652 using UOutType = std::make_unsigned_t<OutType>;
653 return static_cast<OutType>(merge_bit_cast<UOutType>(static_cast<UInType>(hi), static_cast<UInType>(lo)));
654}
655
658template<std::signed_integral OutType, std::unsigned_integral InType>
659[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
660{
661 using UOutType = std::make_unsigned_t<OutType>;
663}
664
665template<typename T>
666[[nodiscard]] constexpr bool to_bool(T&& rhs) noexcept
667 requires(requires(T&& x) { static_cast<bool>(std::forward<T>(x)); })
668{
669 return static_cast<bool>(std::forward<T>(rhs));
670}
671
672template<typename T>
673[[nodiscard]] inline T to_ptr(std::intptr_t value) noexcept
674 requires std::is_pointer_v<T>
675{
676 return reinterpret_cast<T>(value);
677}
678
679template<typename T>
680[[nodiscard]] std::intptr_t to_int(T *ptr) noexcept
681{
682 return reinterpret_cast<std::intptr_t>(ptr);
683}
684
685template<typename T, byte_like Byte>
686[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(std::span<Byte> bytes)
687{
688 using value_type = copy_cv_t<T, Byte>;
689
690 static_assert(std::is_trivially_default_constructible_v<value_type>);
691 static_assert(std::is_trivially_destructible_v<value_type>);
692
693 if (sizeof(value_type) > bytes.size()) {
694 throw std::bad_cast();
695 }
696 hi_axiom_not_null(bytes.data());
697
698 if constexpr (alignof(value_type) != 1) {
699 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
700 throw std::bad_cast();
701 }
702 }
703
704 return *reinterpret_cast<value_type *>(bytes.data());
705}
706
707template<typename T, byte_like Byte>
708[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(std::span<Byte> bytes, size_t n)
709{
710 using value_type = copy_cv_t<T, Byte>;
711
712 static_assert(std::is_trivially_default_constructible_v<value_type>);
713 static_assert(std::is_trivially_destructible_v<value_type>);
714
715 if (sizeof(value_type) * n > bytes.size()) {
716 throw std::bad_cast();
717 }
718 hi_axiom_not_null(bytes.data());
719
720 if constexpr (alignof(value_type) != 1) {
721 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
722 throw std::bad_cast();
723 }
724 }
725
726 return {reinterpret_cast<value_type *>(bytes.data()), n};
727}
728
729template<typename T, byte_like Byte>
730[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(size_t& offset, std::span<Byte> bytes)
731{
732 using value_type = copy_cv_t<T, Byte>;
733
734 static_assert(std::is_trivially_default_constructible_v<value_type>);
735 static_assert(std::is_trivially_destructible_v<value_type>);
736
737 if (sizeof(value_type) + offset > bytes.size()) {
738 throw std::bad_cast();
739 }
740 hi_axiom_not_null(bytes.data());
741
742 hilet data = bytes.data() + offset;
743
744 if constexpr (alignof(value_type) != 1) {
745 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
746 throw std::bad_cast();
747 }
748 }
749
750 offset += sizeof(value_type);
751 return *reinterpret_cast<value_type *>(data);
752}
753
754template<typename T, byte_like Byte>
755[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(size_t& offset, std::span<Byte> bytes, size_t n)
756{
757 using value_type = copy_cv_t<T, Byte>;
758
759 static_assert(std::is_trivially_default_constructible_v<value_type>);
760 static_assert(std::is_trivially_destructible_v<value_type>);
761
762 if (sizeof(value_type) * n + offset > bytes.size()) {
763 throw std::bad_cast();
764 }
765 hi_axiom_not_null(bytes.data());
766
767 hilet data = bytes.data() + offset;
768
769 if constexpr (alignof(value_type) != 1) {
770 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
771 throw std::bad_cast();
772 }
773 }
774
775 offset += sizeof(value_type) * n;
776 return {reinterpret_cast<value_type *>(data), n};
777}
778
779}} // namespace hi::inline v1
780
781hi_warning_pop();
Utilities to assert and bound check.
@ truncate
After the file has been opened, truncate it.
STL namespace.
DOXYGEN BUG.
Definition algorithm.hpp:16
geometry/margins.hpp
Definition lookahead_iterator.hpp:5
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:526
constexpr Out char_cast(In rhs) noexcept
Cast a character.
Definition cast.hpp:550
constexpr OutType low_bit_cast(InType value) noexcept
Return the low half of the input value.
Definition cast.hpp:580
constexpr OutType high_bit_cast(InType value) noexcept
Return the upper half of the input value.
Definition cast.hpp:589
constexpr Out saturate_cast(In rhs) noexcept
Cast a numeric value to an integer saturating on overflow.
Definition cast.hpp:251
constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
Return the upper half of the input value.
Definition cast.hpp:636
constexpr bool can_narrow_cast(In const &rhs) noexcept
Check if a value can be casted to a narrow type.
Definition cast.hpp:276
constexpr auto three_way_compare(Lhs const &lhs, Rhs const &rhs) noexcept
Safely compare two arithmetic values to each other.
Definition compare.hpp:126
constexpr Out down_cast(In *rhs) noexcept
Cast a pointer to a class to its derived class or itself.
Definition cast.hpp:118
constexpr Out up_cast(In *rhs) noexcept
Cast a pointer to a class to its base class or itself.
Definition cast.hpp:65
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:518
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
constexpr Out wide_cast(In const &rhs) noexcept
Cast to a type which can hold all values from the input type.
Definition cast.hpp:175
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)