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
209template<arithmetic Out, arithmetic In>
210[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
211{
212 if constexpr (type_in_range_v<Out, In>) {
213 return static_cast<Out>(rhs);
214 } else {
215 hilet r = static_cast<Out>(rhs);
216 hi_axiom(detail::narrow_validate(r, rhs));
217 return r;
218 }
219}
220
221template<arithmetic Out, arithmetic In>
222[[nodiscard]] constexpr Out round_cast(In rhs) noexcept
223{
224 if constexpr (std::is_floating_point_v<In>) {
225 return narrow_cast<Out>(std::round(rhs));
226 } else {
227 return narrow_cast<Out>(rhs);
228 }
229}
230
231template<arithmetic Out, arithmetic In>
232[[nodiscard]] constexpr Out floor_cast(In rhs) noexcept
233{
234 if constexpr (std::is_floating_point_v<In>) {
235 return narrow_cast<Out>(std::floor(rhs));
236 } else {
237 return narrow_cast<Out>(rhs);
238 }
239}
240
241template<arithmetic Out, arithmetic In>
242[[nodiscard]] constexpr Out ceil_cast(In rhs) noexcept
243{
244 if constexpr (std::is_floating_point_v<In>) {
245 return narrow_cast<Out>(std::ceil(rhs));
246 } else {
247 return narrow_cast<Out>(rhs);
248 }
249}
250
253template<std::integral In>
254[[nodiscard]] constexpr std::make_unsigned_t<In> to_unsigned(In rhs) noexcept
255{
256 return static_cast<std::make_unsigned_t<In>>(rhs);
257}
258
261template<std::integral In>
262[[nodiscard]] constexpr std::make_signed_t<In> to_signed(In rhs) noexcept
263{
264 return static_cast<std::make_signed_t<In>>(rhs);
265}
266
269template<std::integral Out, std::integral In>
270[[nodiscard]] constexpr Out truncate(In rhs) noexcept
271{
272 return static_cast<Out>(to_unsigned(rhs));
273}
274
285template<std::integral Out, std::integral In>
286[[nodiscard]] constexpr Out char_cast(In rhs) noexcept
287{
288 using in_unsigned_type = std::make_unsigned_t<In>;
289 using out_unsigned_type = std::make_unsigned_t<Out>;
290
291 // We cast to unsigned of the same type, so that we don't accidentally sign extent 'char'.
292 auto in_unsigned = static_cast<in_unsigned_type>(rhs);
293 auto out_unsigned = narrow_cast<out_unsigned_type>(in_unsigned);
294 return static_cast<Out>(out_unsigned);
295}
296
307template<std::integral Out>
308[[nodiscard]] constexpr Out char_cast(std::byte rhs) noexcept
309{
310 return char_cast<Out>(static_cast<uint8_t>(rhs));
311}
312
315template<std::unsigned_integral OutType, std::unsigned_integral InType>
316[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
317{
318 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of low_bit_cast must be half the size of the input");
319 return static_cast<OutType>(value);
320}
321
324template<std::unsigned_integral OutType, std::unsigned_integral InType>
325[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
326{
327 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of high_bit_cast must be half the size of the input");
328 return static_cast<OutType>(value >> sizeof(OutType) * CHAR_BIT);
329}
330
333template<std::signed_integral OutType, std::signed_integral InType>
334[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
335{
336 using UInType = std::make_unsigned_t<InType>;
337 using UOutType = std::make_unsigned_t<OutType>;
338 return static_cast<OutType>(low_bit_cast<UOutType>(static_cast<UInType>(value)));
339}
340
343template<std::signed_integral OutType, std::signed_integral InType>
344[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
345{
346 using UInType = std::make_unsigned_t<InType>;
347 using UOutType = std::make_unsigned_t<OutType>;
348 return static_cast<OutType>(high_bit_cast<UOutType>(static_cast<UInType>(value)));
349}
350
353template<std::unsigned_integral OutType, std::signed_integral InType>
354[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
355{
356 using UInType = std::make_unsigned_t<InType>;
357 return low_bit_cast<OutType>(static_cast<UInType>(value));
358}
359
362template<std::unsigned_integral OutType, std::signed_integral InType>
363[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
364{
365 using UInType = std::make_unsigned_t<InType>;
366 return high_bit_cast<OutType>(static_cast<UInType>(value));
367}
368
371template<std::unsigned_integral OutType, std::unsigned_integral InType>
372[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
373{
374 static_assert(sizeof(OutType) == sizeof(InType) * 2, "Return value of merge_bit_cast must be double the size of the input");
375
376 OutType r = static_cast<OutType>(hi);
377 r <<= sizeof(InType) * CHAR_BIT;
378 r |= static_cast<OutType>(lo);
379 return r;
380}
381
384template<std::signed_integral OutType, std::signed_integral InType>
385[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
386{
387 using UInType = std::make_unsigned_t<InType>;
388 using UOutType = std::make_unsigned_t<OutType>;
389 return static_cast<OutType>(merge_bit_cast<UOutType>(static_cast<UInType>(hi), static_cast<UInType>(lo)));
390}
391
394template<std::signed_integral OutType, std::unsigned_integral InType>
395[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
396{
397 using UOutType = std::make_unsigned_t<OutType>;
398 return narrow_cast<OutType>(merge_bit_cast<UOutType>(hi, lo));
399}
400
401[[nodiscard]] constexpr auto to_underlying(scoped_enum auto rhs) noexcept
402{
403 return static_cast<std::underlying_type_t<decltype(rhs)>>(rhs);
404}
405
406template<typename T>
407[[nodiscard]] constexpr bool to_bool(T&& rhs) noexcept
408 requires(requires(T&& x) { static_cast<bool>(std::forward<T>(x)); })
409{
410 return static_cast<bool>(std::forward<T>(rhs));
411}
412
413template<typename T>
414[[nodiscard]] inline T to_ptr(std::intptr_t value) noexcept
415 requires std::is_pointer_v<T>
416{
417 return reinterpret_cast<T>(value);
418}
419
420template<typename T>
421[[nodiscard]] std::intptr_t to_int(T *ptr) noexcept
422{
423 return reinterpret_cast<std::intptr_t>(ptr);
424}
425
426template<typename T, byte_like Byte>
427[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(std::span<Byte> bytes)
428{
429 using value_type = copy_cv_t<T, Byte>;
430
431 static_assert(std::is_trivially_default_constructible_v<value_type>);
432 static_assert(std::is_trivially_destructible_v<value_type>);
433
434 if (sizeof(value_type) > bytes.size()) {
435 throw std::bad_cast();
436 }
437 hi_axiom_not_null(bytes.data());
438
439 if constexpr (alignof(value_type) != 1) {
440 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
441 throw std::bad_cast();
442 }
443 }
444
445 return *reinterpret_cast<value_type *>(bytes.data());
446}
447
448template<typename T, byte_like Byte>
449[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(std::span<Byte> bytes, size_t n)
450{
451 using value_type = copy_cv_t<T, Byte>;
452
453 static_assert(std::is_trivially_default_constructible_v<value_type>);
454 static_assert(std::is_trivially_destructible_v<value_type>);
455
456 if (sizeof(value_type) * n > bytes.size()) {
457 throw std::bad_cast();
458 }
459 hi_axiom_not_null(bytes.data());
460
461 if constexpr (alignof(value_type) != 1) {
462 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
463 throw std::bad_cast();
464 }
465 }
466
467 return {reinterpret_cast<value_type *>(bytes.data()), n};
468}
469
470template<typename T, byte_like Byte>
471[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(size_t& offset, std::span<Byte> bytes)
472{
473 using value_type = copy_cv_t<T, Byte>;
474
475 static_assert(std::is_trivially_default_constructible_v<value_type>);
476 static_assert(std::is_trivially_destructible_v<value_type>);
477
478 if (sizeof(value_type) + offset > bytes.size()) {
479 throw std::bad_cast();
480 }
481 hi_axiom_not_null(bytes.data());
482
483 hilet data = bytes.data() + offset;
484
485 if constexpr (alignof(value_type) != 1) {
486 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
487 throw std::bad_cast();
488 }
489 }
490
491 offset += sizeof(value_type);
492 return *reinterpret_cast<value_type *>(data);
493}
494
495template<typename T, byte_like Byte>
496[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(size_t& offset, std::span<Byte> bytes, size_t n)
497{
498 using value_type = copy_cv_t<T, Byte>;
499
500 static_assert(std::is_trivially_default_constructible_v<value_type>);
501 static_assert(std::is_trivially_destructible_v<value_type>);
502
503 if (sizeof(value_type) * n + offset > bytes.size()) {
504 throw std::bad_cast();
505 }
506 hi_axiom_not_null(bytes.data());
507
508 hilet data = bytes.data() + offset;
509
510 if constexpr (alignof(value_type) != 1) {
511 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
512 throw std::bad_cast();
513 }
514 }
515
516 offset += sizeof(value_type) * n;
517 return {reinterpret_cast<value_type *>(data), n};
518}
519
520} // namespace hi::inline v1
521
522hi_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:372
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:325
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:286
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:254
constexpr OutType low_bit_cast(InType value) noexcept
Return the low half of the input value.
Definition cast.hpp:316
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:270
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:262
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)