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
275template<std::integral Out, std::integral In>
276[[nodiscard]] constexpr Out sign_cast(In rhs) noexcept
277 requires(sizeof(Out) == sizeof(In))
278{
279 return static_cast<Out>(rhs);
280}
281
282template<std::signed_integral Out, std::signed_integral In>
283[[nodiscard]] constexpr Out saturate_cast(In rhs) noexcept
284{
285 if constexpr (sizeof(Out) >= sizeof(In)) {{
286 return static_cast<Out>(rhs);
287 } else {
288 constexpr auto lo = static_cast<In>(std::numeric_limits<Out>::min());
289 constexpr auto hi = static_cast<In>(std::numeric_limits<Out>::max());
290 return static_cast<out>(std::clamp(rhs, lo, hi));
291 }
292}
293
304template<std::integral Out, std::integral In>
305[[nodiscard]] constexpr Out char_cast(In rhs) noexcept
306{
307 using in_unsigned_type = std::make_unsigned_t<In>;
308 using out_unsigned_type = std::make_unsigned_t<Out>;
309
310 // We cast to unsigned of the same type, so that we don't accidentally sign extent 'char'.
311 auto in_unsigned = static_cast<in_unsigned_type>(rhs);
312 auto out_unsigned = narrow_cast<out_unsigned_type>(in_unsigned);
313 return static_cast<Out>(out_unsigned);
314}
315
326template<std::integral Out>
327[[nodiscard]] constexpr Out char_cast(std::byte rhs) noexcept
328{
329 return char_cast<Out>(static_cast<uint8_t>(rhs));
330}
331
334template<std::unsigned_integral OutType, std::unsigned_integral InType>
335[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
336{
337 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of low_bit_cast must be half the size of the input");
338 return static_cast<OutType>(value);
339}
340
343template<std::unsigned_integral OutType, std::unsigned_integral InType>
344[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
345{
346 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of high_bit_cast must be half the size of the input");
347 return static_cast<OutType>(value >> sizeof(OutType) * CHAR_BIT);
348}
349
352template<std::signed_integral OutType, std::signed_integral InType>
353[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
354{
355 using UInType = std::make_unsigned_t<InType>;
356 using UOutType = std::make_unsigned_t<OutType>;
357 return static_cast<OutType>(low_bit_cast<UOutType>(static_cast<UInType>(value)));
358}
359
362template<std::signed_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 using UOutType = std::make_unsigned_t<OutType>;
367 return static_cast<OutType>(high_bit_cast<UOutType>(static_cast<UInType>(value)));
368}
369
372template<std::unsigned_integral OutType, std::signed_integral InType>
373[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
374{
375 using UInType = std::make_unsigned_t<InType>;
376 return low_bit_cast<OutType>(static_cast<UInType>(value));
377}
378
381template<std::unsigned_integral OutType, std::signed_integral InType>
382[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
383{
384 using UInType = std::make_unsigned_t<InType>;
385 return high_bit_cast<OutType>(static_cast<UInType>(value));
386}
387
390template<std::unsigned_integral OutType, std::unsigned_integral InType>
391[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
392{
393 static_assert(sizeof(OutType) == sizeof(InType) * 2, "Return value of merge_bit_cast must be double the size of the input");
394
395 OutType r = static_cast<OutType>(hi);
396 r <<= sizeof(InType) * CHAR_BIT;
397 r |= static_cast<OutType>(lo);
398 return r;
399}
400
403template<std::signed_integral OutType, std::signed_integral InType>
404[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
405{
406 using UInType = std::make_unsigned_t<InType>;
407 using UOutType = std::make_unsigned_t<OutType>;
408 return static_cast<OutType>(merge_bit_cast<UOutType>(static_cast<UInType>(hi), static_cast<UInType>(lo)));
409}
410
413template<std::signed_integral OutType, std::unsigned_integral InType>
414[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
415{
416 using UOutType = std::make_unsigned_t<OutType>;
417 return narrow_cast<OutType>(merge_bit_cast<UOutType>(hi, lo));
418}
419
420[[nodiscard]] constexpr auto to_underlying(scoped_enum auto rhs) noexcept
421{
422 return static_cast<std::underlying_type_t<decltype(rhs)>>(rhs);
423}
424
425template<typename T>
426[[nodiscard]] constexpr bool to_bool(T&& rhs) noexcept
427 requires(requires(T&& x) { static_cast<bool>(std::forward<T>(x)); })
428{
429 return static_cast<bool>(std::forward<T>(rhs));
430}
431
432template<typename T>
433[[nodiscard]] inline T to_ptr(std::intptr_t value) noexcept
434 requires std::is_pointer_v<T>
435{
436 return reinterpret_cast<T>(value);
437}
438
439template<typename T>
440[[nodiscard]] std::intptr_t to_int(T *ptr) noexcept
441{
442 return reinterpret_cast<std::intptr_t>(ptr);
443}
444
445template<typename T, byte_like Byte>
446[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(std::span<Byte> bytes)
447{
448 using value_type = copy_cv_t<T, Byte>;
449
450 static_assert(std::is_trivially_default_constructible_v<value_type>);
451 static_assert(std::is_trivially_destructible_v<value_type>);
452
453 if (sizeof(value_type) > bytes.size()) {
454 throw std::bad_cast();
455 }
456 hi_axiom_not_null(bytes.data());
457
458 if constexpr (alignof(value_type) != 1) {
459 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
460 throw std::bad_cast();
461 }
462 }
463
464 return *reinterpret_cast<value_type *>(bytes.data());
465}
466
467template<typename T, byte_like Byte>
468[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(std::span<Byte> bytes, size_t n)
469{
470 using value_type = copy_cv_t<T, Byte>;
471
472 static_assert(std::is_trivially_default_constructible_v<value_type>);
473 static_assert(std::is_trivially_destructible_v<value_type>);
474
475 if (sizeof(value_type) * n > bytes.size()) {
476 throw std::bad_cast();
477 }
478 hi_axiom_not_null(bytes.data());
479
480 if constexpr (alignof(value_type) != 1) {
481 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
482 throw std::bad_cast();
483 }
484 }
485
486 return {reinterpret_cast<value_type *>(bytes.data()), n};
487}
488
489template<typename T, byte_like Byte>
490[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(size_t& offset, std::span<Byte> bytes)
491{
492 using value_type = copy_cv_t<T, Byte>;
493
494 static_assert(std::is_trivially_default_constructible_v<value_type>);
495 static_assert(std::is_trivially_destructible_v<value_type>);
496
497 if (sizeof(value_type) + offset > bytes.size()) {
498 throw std::bad_cast();
499 }
500 hi_axiom_not_null(bytes.data());
501
502 hilet data = bytes.data() + offset;
503
504 if constexpr (alignof(value_type) != 1) {
505 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
506 throw std::bad_cast();
507 }
508 }
509
510 offset += sizeof(value_type);
511 return *reinterpret_cast<value_type *>(data);
512}
513
514template<typename T, byte_like Byte>
515[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(size_t& offset, std::span<Byte> bytes, size_t n)
516{
517 using value_type = copy_cv_t<T, Byte>;
518
519 static_assert(std::is_trivially_default_constructible_v<value_type>);
520 static_assert(std::is_trivially_destructible_v<value_type>);
521
522 if (sizeof(value_type) * n + offset > bytes.size()) {
523 throw std::bad_cast();
524 }
525 hi_axiom_not_null(bytes.data());
526
527 hilet data = bytes.data() + offset;
528
529 if constexpr (alignof(value_type) != 1) {
530 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
531 throw std::bad_cast();
532 }
533 }
534
535 offset += sizeof(value_type) * n;
536 return {reinterpret_cast<value_type *>(data), n};
537}
538
539
540} // namespace hi::inline v1
541
542hi_warning_pop();
Utilities to assert and bound check.
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:238
#define hi_axiom_not_null(expression,...)
Assert if an expression is not nullptr.
Definition assert.hpp:257
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
@ truncate
After the file has been opened, truncate it.
DOXYGEN BUG.
Definition algorithm.hpp:13
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 min(T... args)
T round(T... args)