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 "terminate.hpp"
13#include "../macros.hpp"
14#include "assert.hpp"
15#include "compare.hpp"
16#include "concepts.hpp"
17#include "exception.hpp"
18#include <type_traits>
19#include <typeinfo>
20#include <concepts>
21#include <limits>
22#include <span>
23#include <numeric>
24#include <bit>
25#include <cmath>
26
27hi_export_module(hikogui.utility.cast);
28
29hi_warning_push();
30// C26472: Don't use static_cast for arithmetic conversions, Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
31// This file implements narrow_cast().
32hi_warning_ignore_msvc(26472);
33// C26467: Converting from floating point to unsigned integral types results in non-portable code if the double/float has
34// a negative value. Use gsl::narrow_cast or gsl::naroow instead to guard against undefined behavior and potential data loss
35// (es.46).
36// This file implements narrow_cast().
37hi_warning_ignore_msvc(26467);
38// C26496: The variable 'r' does not change after construction, mark it as const (con.4).
39// False positive
40hi_warning_ignore_msvc(26496);
41// C26466: Don't use static_cast downcast. A cast from a polymorphic type should use dynamic_cast (type.2)
42// Used in down_cast<>() specifically for doing this savely.
43hi_warning_ignore_msvc(26466);
44// C26474: Don't cast between pointer types when the conversion could be implicit (type.1).
45// Since these functions are templates this happens.
46hi_warning_ignore_msvc(26474);
47// C4702 unreachable code: Suppressed due intrinsics and std::is_constant_evaluated()
48hi_warning_ignore_msvc(4702);
49
50hi_export namespace hi { inline namespace v1 {
51
52template<typename T>
53[[nodiscard]] constexpr T copy(T value) noexcept
54{
55 return value;
56}
57
65template<typename Out, typename In>
66[[nodiscard]] constexpr Out up_cast(In *rhs) noexcept
67{
68 using out_type = std::remove_pointer_t<Out>;
69
70 static_assert(std::is_pointer_v<Out>, "up_cast() Out template paramater must be a pointer if the input is a pointer.");
71 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.");
72 static_assert(std::is_base_of_v<out_type, In>, "up_cast() may only be used to cast to a base-type.");
73
74 return static_cast<Out>(rhs);
75}
76
84template<typename Out>
85[[nodiscard]] constexpr Out up_cast(nullptr_t) noexcept
86{
87 static_assert(std::is_pointer_v<Out>, "up_cast() Out template paramater must be a pointer.");
88 return nullptr;
89}
90
98template<typename Out, typename In>
99[[nodiscard]] constexpr Out up_cast(In& rhs) noexcept
100{
101 using out_type = std::remove_reference_t<Out>;
102
103 static_assert(std::is_reference_v<Out>, "up_cast() Out template paramater must be a reference if the input is a reference.");
104 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.");
105 static_assert(std::is_base_of_v<out_type, In>, "up_cast() may only be used to cast to a base-type.");
106
107 return static_cast<Out>(rhs);
108}
109
118template<typename Out, typename In>
119[[nodiscard]] constexpr Out down_cast(In *rhs) noexcept
120{
121 using out_type = std::remove_pointer_t<Out>;
122
123 static_assert(std::is_pointer_v<Out>, "down_cast() Out template paramater must be a pointer if the input is a pointer.");
124 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.");
125 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.");
126
127 if constexpr (not std::is_base_of_v<out_type, In>) {
128 hi_axiom(rhs == nullptr or dynamic_cast<Out>(rhs) != nullptr);
129 }
130 return static_cast<Out>(rhs);
131}
132
138template<typename Out>
139[[nodiscard]] constexpr Out down_cast(nullptr_t) noexcept
140 requires std::is_pointer_v<Out>
141{
142 return nullptr;
143}
144
152template<typename Out, typename In>
153[[nodiscard]] constexpr Out down_cast(In& rhs) noexcept
154{
155 using out_type = std::remove_reference_t<Out>;
156
157 static_assert(std::is_reference_v<Out>, "down_cast() Out template paramater must be a reference if the input is a reference.");
158 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.");
159 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.");
160
161 if constexpr (not std::is_base_of_v<out_type, In>) {
162 hi_axiom(dynamic_cast<std::add_pointer_t<out_type>>(std::addressof(rhs)) != nullptr);
163 }
164 return static_cast<Out>(rhs);
165}
166
175template<typename Out, std::same_as<Out> In>
176[[nodiscard]] constexpr Out wide_cast(In const& rhs) noexcept
177{
178 return rhs;
179}
180
188template<std::floating_point Out, std::floating_point In>
189[[nodiscard]] constexpr Out wide_cast(In const& rhs) noexcept
190 requires(not std::same_as<In, Out>)
191{
192 static_assert(
194 "wide_cast() is only allowed to a floating point of the same or larger size.");
195
196 return static_cast<Out>(rhs);
197}
198
206template<std::integral Out, std::integral In>
207[[nodiscard]] constexpr Out wide_cast(In rhs) noexcept
208 requires(not std::same_as<In, Out>)
209{
210 static_assert(
212 "wide_cast() is only allowed if the input is unsigned or if both input and output have the same signess.");
213
214 static_assert(
216 "wide_cast() is only allowed to an integer of the same or larger size.");
217
218 return static_cast<Out>(rhs);
219}
220
235template<std::floating_point Out, std::integral In>
236[[nodiscard]] constexpr Out wide_cast(In rhs) noexcept
237{
238 static_assert(
240 "wide_cast() is only allowed if the input can be represented with perfect accuracy by the floating point output type.");
241
242 return static_cast<Out>(rhs);
243}
244
251template<std::integral Out, arithmetic In>
252[[nodiscard]] constexpr Out saturate_cast(In rhs) noexcept
253{
254 if constexpr (std::is_floating_point_v<In>) {
255 if (std::isnan(rhs)) {
256 return Out{0};
257 }
258 }
259
260 if (three_way_compare(rhs, std::numeric_limits<Out>::lowest()) != std::strong_ordering::greater) {
262 } else if (three_way_compare(rhs, std::numeric_limits<Out>::max()) != std::strong_ordering::less) {
264 } else {
265 return static_cast<Out>(rhs);
266 }
267}
268
276template<typename Out, std::same_as<Out> In>
277[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
278{
279 return true;
280}
281
289template<std::floating_point Out, std::floating_point In>
290[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
291 requires(not std::same_as<In, Out>)
292{
294 // cast is allowed when the input is NaN, infinite or within the range of the output type.
295 return rhs != rhs or rhs == std::numeric_limits<In>::infinity() or rhs == -std::numeric_limits<In>::infinity() or
297 }
298
299 return true;
300}
301
309template<std::integral Out, std::integral In>
310[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
311 requires(not std::same_as<In, Out>)
312{
316 if (rhs < static_cast<In>(std::numeric_limits<Out>::lowest())) {
317 return false;
318 }
319 }
320 return rhs <= static_cast<In>(std::numeric_limits<Out>::max());
321 }
322
323 } else if constexpr (std::numeric_limits<In>::is_signed) {
324 if (rhs < 0) {
325 return false;
326 }
327
329 return rhs <= static_cast<In>(std::numeric_limits<Out>::max());
330 }
331
332 } else {
334 return rhs <= static_cast<In>(std::numeric_limits<Out>::max());
335 }
336 }
337
338 return true;
339}
340
348template<std::floating_point Out, std::integral In>
349[[nodiscard]] constexpr bool can_narrow_cast(In const& rhs) noexcept
350{
353 constexpr auto max = (1LL << std::numeric_limits<Out>::digits) - 1;
354 constexpr auto lowest = -max;
355
356 return rhs >= lowest and rhs <= max;
357
358 } else {
359 constexpr auto max = (1ULL << std::numeric_limits<Out>::digits) - 1;
360
361 return rhs <= max;
362 }
363 }
364
365 return true;
366}
367
377template<typename Out, std::same_as<Out> In>
378[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
379{
380 return rhs;
381}
382
392template<std::floating_point Out, std::floating_point In>
393[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
394 requires(not std::same_as<In, Out>)
395{
397 // cast is allowed when the input is NaN, infinite or within the range of the output type.
398 hi_axiom(
399 rhs != rhs or rhs == std::numeric_limits<In>::infinity() or rhs == -std::numeric_limits<In>::infinity() or
401 }
402
403 return static_cast<Out>(rhs);
404}
405
415template<std::integral Out, std::integral In>
416[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
417 requires(not std::same_as<In, Out>)
418{
422 hi_axiom(rhs >= static_cast<In>(std::numeric_limits<Out>::lowest()));
423 }
424 hi_axiom(rhs <= static_cast<In>(std::numeric_limits<Out>::max()));
425 }
426
427 } else if constexpr (std::numeric_limits<In>::is_signed) {
428 hi_axiom(rhs >= 0);
430 hi_axiom(rhs <= static_cast<In>(std::numeric_limits<Out>::max()));
431 }
432
433 } else {
435 hi_axiom(rhs <= static_cast<In>(std::numeric_limits<Out>::max()));
436 }
437 }
438
439 return static_cast<Out>(rhs);
440}
441
451template<std::floating_point Out, std::integral In>
452[[nodiscard]] constexpr Out narrow_cast(In const& rhs) noexcept
453{
456 constexpr auto max = (1LL << std::numeric_limits<Out>::digits) - 1;
457 constexpr auto lowest = -max;
458
459 hi_axiom(rhs >= lowest and rhs <= max);
460
461 } else {
462 constexpr auto max = (1ULL << std::numeric_limits<Out>::digits) - 1;
463
464 hi_axiom(rhs <= max);
465 }
466 }
467
468 return static_cast<Out>(rhs);
469}
470
471template<std::integral Out, std::floating_point In>
472[[nodiscard]] constexpr bool can_round_cast(In rhs) noexcept
473{
474 auto const rhs_ = std::round(rhs);
475 return rhs_ >= std::numeric_limits<Out>::lowest() and rhs_ <= std::numeric_limits<Out>::max();
476}
477
478template<std::integral Out, std::floating_point In>
479[[nodiscard]] constexpr bool can_floor_cast(In rhs) noexcept
480{
481 auto const rhs_ = std::floor(rhs);
482 return rhs_ >= std::numeric_limits<Out>::lowest() and rhs_ <= std::numeric_limits<Out>::max();
483}
484
485template<std::integral Out, std::floating_point In>
486[[nodiscard]] constexpr bool can_ceil_cast(In rhs) noexcept
487{
488 auto const rhs_ = std::ceil(rhs);
489 return rhs_ >= std::numeric_limits<Out>::lowest() and rhs_ <= std::numeric_limits<Out>::max();
490}
491
492template<std::integral Out, std::floating_point In>
493[[nodiscard]] constexpr Out round_cast(In rhs) noexcept
494{
495 auto const lowest = static_cast<long double>(std::numeric_limits<Out>::lowest());
496 auto const highest = static_cast<long double>(std::numeric_limits<Out>::max());
497
498 auto const rhs_ = std::round(rhs);
499 hi_axiom(rhs_ >= lowest and rhs_ <= highest);
500 return static_cast<Out>(rhs_);
501}
502
503template<std::integral Out, std::floating_point In>
504[[nodiscard]] constexpr Out floor_cast(In rhs) noexcept
505{
506 auto const lowest = static_cast<long double>(std::numeric_limits<Out>::lowest());
507 auto const highest = static_cast<long double>(std::numeric_limits<Out>::max());
508
509 auto const rhs_ = std::floor(rhs);
510 hi_axiom(rhs_ >= lowest and rhs_ <= highest);
511 return static_cast<Out>(rhs_);
512}
513
514template<std::integral Out, std::floating_point In>
515[[nodiscard]] constexpr Out ceil_cast(In rhs) noexcept
516{
517 auto const lowest = static_cast<long double>(std::numeric_limits<Out>::lowest());
518 auto const highest = static_cast<long double>(std::numeric_limits<Out>::max());
519
520 auto const rhs_ = std::ceil(rhs);
521 hi_axiom(rhs_ >= lowest and rhs_ <= highest);
522 return static_cast<Out>(rhs_);
523}
524
527template<std::integral In>
528[[nodiscard]] constexpr std::make_unsigned_t<In> to_unsigned(In rhs) noexcept
529{
530 return static_cast<std::make_unsigned_t<In>>(rhs);
531}
532
535template<std::integral In>
536[[nodiscard]] constexpr std::make_signed_t<In> to_signed(In rhs) noexcept
537{
538 return static_cast<std::make_signed_t<In>>(rhs);
539}
540
543template<std::integral Out, std::integral In>
544[[nodiscard]] constexpr Out truncate(In rhs) noexcept
545{
546 return static_cast<Out>(to_unsigned(rhs));
547}
548
559template<std::integral Out, std::integral In>
560[[nodiscard]] constexpr Out char_cast(In rhs) noexcept
561{
562 using in_unsigned_type = std::make_unsigned_t<In>;
563 using out_unsigned_type = std::make_unsigned_t<Out>;
564
565 // We cast to unsigned of the same type, so that we don't accidentally sign extent 'char'.
566 auto in_unsigned = static_cast<in_unsigned_type>(rhs);
567 auto out_unsigned = narrow_cast<out_unsigned_type>(in_unsigned);
568 return static_cast<Out>(out_unsigned);
569}
570
581template<std::integral Out>
582[[nodiscard]] constexpr Out char_cast(std::byte rhs) noexcept
583{
584 return char_cast<Out>(static_cast<uint8_t>(rhs));
585}
586
589template<std::unsigned_integral OutType, std::unsigned_integral InType>
590[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
591{
592 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of low_bit_cast must be half the size of the input");
593 return static_cast<OutType>(value);
594}
595
598template<std::unsigned_integral OutType, std::unsigned_integral InType>
599[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
600{
601 static_assert(sizeof(OutType) * 2 == sizeof(InType), "Return value of high_bit_cast must be half the size of the input");
602 return static_cast<OutType>(value >> sizeof(OutType) * CHAR_BIT);
603}
604
607template<std::signed_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 using UOutType = std::make_unsigned_t<OutType>;
612 return static_cast<OutType>(low_bit_cast<UOutType>(static_cast<UInType>(value)));
613}
614
617template<std::signed_integral OutType, std::signed_integral InType>
618[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
619{
620 using UInType = std::make_unsigned_t<InType>;
621 using UOutType = std::make_unsigned_t<OutType>;
622 return static_cast<OutType>(high_bit_cast<UOutType>(static_cast<UInType>(value)));
623}
624
627template<std::unsigned_integral OutType, std::signed_integral InType>
628[[nodiscard]] constexpr OutType low_bit_cast(InType value) noexcept
629{
630 using UInType = std::make_unsigned_t<InType>;
631 return low_bit_cast<OutType>(static_cast<UInType>(value));
632}
633
636template<std::unsigned_integral OutType, std::signed_integral InType>
637[[nodiscard]] constexpr OutType high_bit_cast(InType value) noexcept
638{
639 using UInType = std::make_unsigned_t<InType>;
640 return high_bit_cast<OutType>(static_cast<UInType>(value));
641}
642
645template<std::unsigned_integral OutType, std::unsigned_integral InType>
646[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
647{
648 static_assert(sizeof(OutType) == sizeof(InType) * 2, "Return value of merge_bit_cast must be double the size of the input");
649
650 OutType r = static_cast<OutType>(hi);
651 r <<= sizeof(InType) * CHAR_BIT;
652 r |= static_cast<OutType>(lo);
653 return r;
654}
655
658template<std::signed_integral OutType, std::signed_integral InType>
659[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
660{
661 using UInType = std::make_unsigned_t<InType>;
662 using UOutType = std::make_unsigned_t<OutType>;
663 return static_cast<OutType>(merge_bit_cast<UOutType>(static_cast<UInType>(hi), static_cast<UInType>(lo)));
664}
665
668template<std::signed_integral OutType, std::unsigned_integral InType>
669[[nodiscard]] constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
670{
671 using UOutType = std::make_unsigned_t<OutType>;
672 return narrow_cast<OutType>(merge_bit_cast<UOutType>(hi, lo));
673}
674
675template<typename T>
676[[nodiscard]] constexpr bool to_bool(T&& rhs) noexcept
677 requires(requires(T&& x) { static_cast<bool>(std::forward<T>(x)); })
678{
679 return static_cast<bool>(std::forward<T>(rhs));
680}
681
688template<typename T>
689[[nodiscard]] constexpr T to_mask(bool v) noexcept
690{
691 using large_bool = make_intxx_t<sizeof(T) * CHAR_BIT>;
692
693 auto r = static_cast<large_bool>(v);
694 r <<= sizeof(T) * CHAR_BIT - 1;
695 r >>= sizeof(T) * CHAR_BIT - 1;
696 return std::bit_cast<T>(r);
697}
698
699template<typename T>
700[[nodiscard]] inline T to_ptr(std::intptr_t value) noexcept
701 requires std::is_pointer_v<T>
702{
703 return reinterpret_cast<T>(value);
704}
705
706template<typename T>
707[[nodiscard]] std::intptr_t to_int(T *ptr) noexcept
708{
709 return reinterpret_cast<std::intptr_t>(ptr);
710}
711
712template<typename T, byte_like Byte>
713[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(std::span<Byte> bytes)
714{
715 using value_type = copy_cv_t<T, Byte>;
716
717 static_assert(std::is_trivially_default_constructible_v<value_type>);
718 static_assert(std::is_trivially_destructible_v<value_type>);
719
720 if (sizeof(value_type) > bytes.size()) {
721 throw std::bad_cast();
722 }
723 hi_axiom_not_null(bytes.data());
724
725 if constexpr (alignof(value_type) != 1) {
726 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
727 throw std::bad_cast();
728 }
729 }
730
731 return *reinterpret_cast<value_type *>(bytes.data());
732}
733
734template<typename T, byte_like Byte>
735[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(std::span<Byte> bytes, size_t n)
736{
737 using value_type = copy_cv_t<T, Byte>;
738
739 static_assert(std::is_trivially_default_constructible_v<value_type>);
740 static_assert(std::is_trivially_destructible_v<value_type>);
741
742 if (sizeof(value_type) * n > bytes.size()) {
743 throw std::bad_cast();
744 }
745 hi_axiom_not_null(bytes.data());
746
747 if constexpr (alignof(value_type) != 1) {
748 if (std::bit_cast<std::uintptr_t>(bytes.data()) % alignof(value_type) != 0) {
749 throw std::bad_cast();
750 }
751 }
752
753 return {reinterpret_cast<value_type *>(bytes.data()), n};
754}
755
756template<typename T, byte_like Byte>
757[[nodiscard]] copy_cv_t<T, Byte>& implicit_cast(size_t& offset, std::span<Byte> bytes)
758{
759 using value_type = copy_cv_t<T, Byte>;
760
761 static_assert(std::is_trivially_default_constructible_v<value_type>);
762 static_assert(std::is_trivially_destructible_v<value_type>);
763
764 if (sizeof(value_type) + offset > bytes.size()) {
765 throw std::bad_cast();
766 }
767 hi_axiom_not_null(bytes.data());
768
769 auto const data = bytes.data() + offset;
770
771 if constexpr (alignof(value_type) != 1) {
772 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
773 throw std::bad_cast();
774 }
775 }
776
777 offset += sizeof(value_type);
778 return *reinterpret_cast<value_type *>(data);
779}
780
781template<typename T, byte_like Byte>
782[[nodiscard]] std::span<copy_cv_t<T, Byte>> implicit_cast(size_t& offset, std::span<Byte> bytes, size_t n)
783{
784 using value_type = copy_cv_t<T, Byte>;
785
786 static_assert(std::is_trivially_default_constructible_v<value_type>);
787 static_assert(std::is_trivially_destructible_v<value_type>);
788
789 if (sizeof(value_type) * n + offset > bytes.size()) {
790 throw std::bad_cast();
791 }
792 hi_axiom_not_null(bytes.data());
793
794 auto const data = bytes.data() + offset;
795
796 if constexpr (alignof(value_type) != 1) {
797 if (std::bit_cast<std::uintptr_t>(data) % alignof(value_type) != 0) {
798 throw std::bad_cast();
799 }
800 }
801
802 offset += sizeof(value_type) * n;
803 return {reinterpret_cast<value_type *>(data), n};
804}
805
806}} // namespace hi::inline v1
807
808hi_warning_pop();
Utilities to assert and bound check.
Utilities for throwing exceptions and terminating the application.
@ truncate
After the file has been opened, truncate it.
STL namespace.
The HikoGUI namespace.
Definition array_generic.hpp:20
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:536
constexpr Out char_cast(In rhs) noexcept
Cast a character.
Definition cast.hpp:560
constexpr OutType low_bit_cast(InType value) noexcept
Return the low half of the input value.
Definition cast.hpp:590
constexpr OutType high_bit_cast(InType value) noexcept
Return the upper half of the input value.
Definition cast.hpp:599
constexpr Out saturate_cast(In rhs) noexcept
Cast a numeric value to an integer saturating on overflow.
Definition cast.hpp:252
constexpr OutType merge_bit_cast(InType hi, InType lo) noexcept
Return the upper half of the input value.
Definition cast.hpp:646
constexpr bool can_narrow_cast(In const &rhs) noexcept
Check if a value can be casted to a narrow type.
Definition cast.hpp:277
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:119
constexpr Out up_cast(In *rhs) noexcept
Cast a pointer to a class to its base class or itself.
Definition cast.hpp:66
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:528
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:378
constexpr Out wide_cast(In const &rhs) noexcept
Cast to a type which can hold all values from the input type.
Definition cast.hpp:176
constexpr T to_mask(bool v) noexcept
Create a mask from a boolean value.
Definition cast.hpp:689
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
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)