UgMisc 0.2-128
Miscellaneous C++ header library
Loading...
Searching...
No Matches
int_finder.hpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: © 2025 Larry Chips <larry@larrychips.net>
3 * SPDX-Licence-Identifier: MIT
4 */
5#ifndef UGMISC_INT_FINDER_HPP
6#define UGMISC_INT_FINDER_HPP
7
48
49#include <cstdint>
50#include <limits>
51#include <string_view>
52#include <type_traits>
53#include "ugmisc/bitops.hpp"
54#include "ugmisc/features.hpp"
55#include "ugmisc/member.hpp"
56#include "ugmisc/quote.hpp"
57#include "ugmisc/templated_callable_loop.hpp"
58
59
60namespace ugmisc {
61
62
64enum int_sign { INVALID_SIGN = 0, UNSIGNED = 1, SIGNED = 2 };
65
66
67
68
69constexpr int_sign flip( int_sign s ) {
70 return static_cast<int_sign>(((unsigned)s) ^ 3);
71}
72
73
74
75
81enum sign_opt : std::uint8_t {
82 SELECT_SIGN_OPTION_NONE = 0,
83 SELECT_UNSIGNED = 1,
84 SELECT_SIGNED = 2,
88
89 SELECT_SIGN_OPTION_MAX = SELECT_VALUE_SIGN,
90 SELECT_SIGN_OPTION_MASK = 7,
91};
92
93
94
95
96constexpr std::string_view get_name(sign_opt opt) {
97 switch( opt ) {
98 case SELECT_SIGN_OPTION_NONE:
99 return "NONE";
100 case SELECT_UNSIGNED:
101 return "SELECT_UNSIGNED";
102 case SELECT_SIGNED:
103 return "SELECT_SIGNED";
104 case SELECT_TYPE_SIGN:
105 return "SELECT_TYPE_SIGN";
106 case SELECT_VALUE_SIGN:
107 return "SELECT_VALUE_SIGN";
108 default:
109 return "INVALID";
110 }
111};
112
113
114
115
116
117template<class T>
118constexpr int_sign sign(sign_opt opt, T v) {
119
120 switch( opt ) {
121 case SELECT_UNSIGNED: return UNSIGNED;
122 case SELECT_SIGNED: return SIGNED;
123 case SELECT_TYPE_SIGN:
124 return std::is_signed_v<T> ? SIGNED : UNSIGNED;
126 return v < 0 ? SIGNED : UNSIGNED;
127 default:
128 return INVALID_SIGN;
129 }
130}
131
132
133
134
138enum sign_flag : std::uint8_t {
139 SELECT_SIGN_FLAGS_NONE,
150
157
159};
160
161
162constexpr sign_flag operator~(sign_flag f) {
163 return static_cast<sign_flag>((~(std::uint8_t)f)&(std::uint8_t)SELECT_SIGN_FLAGS_MASK);
164}
165
166
167constexpr sign_flag operator|(sign_flag a, sign_flag b) {
168 return static_cast<sign_flag>((std::uint8_t)a | (std::uint8_t)b);
169}
170
171
172constexpr bool smallest(sign_flag s) {
173 return (std::uint8_t)SELECT_SIGN_SMALLEST & (std::uint8_t)s;
174}
175
176
177constexpr bool allow_value_sign_loss(sign_flag s) {
178 return (std::uint8_t)SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS & (std::uint8_t)s;
179}
180
181
182/*
183 * This might have to be a string instead of a string_view if we add more
184 * flags, because we can't just special case every combination.
185 *
186 * Then we would have to ditch the constexpr too.
187 */
188constexpr std::string_view get_name(sign_flag flags) {
189 bool smol = smallest(flags);
190 bool signloss = allow_value_sign_loss(flags);
191 if ( smol && signloss ) {
192 return "SELECT_SIGN_SMALLEST|SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS";
193 } else if ( smol ) {
194 return "SELECT_SIGN_SMALLEST";
195 } else if ( signloss ) {
196 return "SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS";
197 } else {
198 return "NONE";
199 }
200}
201
202
203
204
206namespace intfind_ {
207
208
209
210
211template<class T>
212inline constexpr bool is_opt = std::is_convertible_v<T, sign_opt>;
213
214template<class T>
215inline constexpr bool is_flag = std::is_convertible_v<T, sign_flag>;
216
217
218
219
225template< class T >
226constexpr sign_opt sign_opt_or_not(T t) {
227 if constexpr ( is_opt<T> ) {
228 return static_cast<sign_opt>(t);
229 } else if constexpr ( is_flag<T> ) {
230 return SELECT_SIGN_OPTION_NONE;
231 } else {
232 static_assert( false );
233 }
234}
235
236
237template<class T> constexpr bool is_non_null_opt(T t) {
238 return (unsigned)sign_opt_or_not(t);
239}
240
241
242template<class T> constexpr sign_flag sign_flag_or_not(T t) {
243 if constexpr ( is_flag<T> ) {
244 return static_cast<sign_flag>(t);
245 } else if constexpr ( is_opt<T> ) {
246 return SELECT_SIGN_FLAGS_NONE;
247 } else {
248 static_assert(
249 false,
250 "sign_flag_or_not takes sign_flag and sign_opt, "
251 "and types convertible to them."
252 );
253 }
254}
255
256
257
258
264template<auto...Opts>
265constexpr sign_opt combine_sign_opts_f()
266{
267 constexpr sign_opt opt_array[sizeof...(Opts)] = { sign_opt_or_not(Opts)... };
268 sign_opt found_opt = SELECT_SIGN_OPTION_NONE;
269 for( sign_opt opt: opt_array ) {
270 if ( opt == SELECT_SIGN_OPTION_NONE ) {
271 continue;
272 }
273 if ( found_opt != SELECT_SIGN_OPTION_NONE && opt != found_opt ) {
274 return SELECT_SIGN_OPTION_NONE;
275 }
276 found_opt = opt;
277 }
278 return found_opt;
279}
280
281
282
283
284template<auto...Opts> static constexpr sign_opt combined_sign_opts
285 = combine_sign_opts_f<Opts...>();
286
287
288template<auto...Flags> static constexpr sign_flag combined_sign_flags
289 = (SELECT_SIGN_FLAGS_NONE | ... | sign_flag_or_not(Flags));
290
291
292
293
294template<auto...Opt>
295static constexpr bool valid_sign_opts = (unsigned)combined_sign_opts<Opt...>;
296
297
298
299
300template<auto...Opt>
301static constexpr bool any_sign_opts = (... || is_non_null_opt(Opt));
302
303
304
305
313template<bool HasOpt>
314class flag_opt_combo {
315 const sign_flag m_flags;
316
317public:
318 flag_opt_combo(const flag_opt_combo&) = default;
319 constexpr flag_opt_combo(sign_flag sflags) : m_flags(sflags) {}
320
321 constexpr sign_flag flags() const { return m_flags; }
322 constexpr sign_opt safe_opt() const { return SELECT_SIGN_OPTION_NONE; }
323 constexpr sign_opt opt() const {
324 static_assert(
325 false,
326 "This flag_opt_combo does not have an option."
327 );
328 return safe_opt();
329 }
330
331 constexpr operator sign_flag() const { return flags(); }
332};
333
334
335template<>
336class flag_opt_combo<true> : public flag_opt_combo<false> {
337 const sign_opt m_opt;
338
339public:
340 flag_opt_combo(const flag_opt_combo&) = default;
341 constexpr flag_opt_combo(sign_opt sopt, sign_flag sflags)
342 : flag_opt_combo<false>(sflags), m_opt(sopt)
343 {}
344
345 constexpr sign_opt opt() const { return m_opt; }
346 constexpr sign_opt safe_opt() const { return opt(); }
347
348 constexpr operator sign_opt() const { return opt(); }
349};
350
351
352
353
354template<auto...args>
355constexpr auto combine_flags_opts()
356{
357 constexpr sign_flag flags = combined_sign_flags<args...>;
358 constexpr sign_opt opt = combined_sign_opts<args...>;
359 constexpr bool valid_opts = valid_sign_opts<args...>;
360 constexpr bool any_opts = any_sign_opts<args...>;
361
362 if constexpr ( any_opts ) {
363 static_assert( valid_opts );
364 return flag_opt_combo<true>{opt, flags};
365 } else {
366 return flag_opt_combo<false>{flags};
367 }
368}
369
370
371
372
373
374} /* intfind_ */
376// INTERNAL
377
378
379
380
381#ifndef UGMISC_INT_FINDER_MAX_INT_WIDTH
386# define UGMISC_INT_FINDER_MAX_INT_WIDTH (1U << 12);
387#endif
394
395
396
397
407template<unsigned N> struct get_int_types {};
408
409
410template<> struct get_int_types<8> {
411 using unsigned_type = std::uint8_t;
412 using signed_type = std::int8_t;
413};
414
415template<> struct get_int_types<16> {
416 using unsigned_type = std::uint16_t;
417 using signed_type = std::int16_t;
418};
419
420template<> struct get_int_types<32> {
421#ifndef UGMISC_TEST_INT_FINDER_SKIP_U32
422 using unsigned_type = std::uint32_t;
423#endif
424 using signed_type = std::int32_t;
425};
426
427template<> struct get_int_types<64> {
428 using unsigned_type = std::uint64_t;
429 using signed_type = std::int64_t;
430};
431
432
433
434
436namespace intfind_ {
437
438
439
440
441constexpr unsigned round_up_to_pow2(unsigned x) {
442 unsigned hi = 1U << bitwidth(x);
443 unsigned lo = hi >> 1;
444 return x ? (lo == x ? lo : hi) : 1;
445}
446
447
448
449
450/*
451 * This allows us to test the existence of a type easily.
452 *
453 * Resulting declarations are listed below for clarity.
454 */
455UGMISC_DECL_MEMBER_ACCESS(utype, unsigned_type);
456UGMISC_DECL_MEMBER_ACCESS(stype, signed_type);
457// has_member_type_unsigned_type< T >; // T can be a type_list.
458// member_type_unsigned_type< T, D (optional) > // T can be type_list,
459 // D is a default type.
460// member_type_test_unsigned_type< T > // Usually not used directly.
461// has_member_type_signed_type< T >; // T can be a type_list.
462// member_type_signed_type< T, D (optional) > // T can be type_list,
463 // D is a default type.
464// member_type_test_signed_type< T > // Usually not used directly.
465
466
467
468template<unsigned N>
469constexpr bool check_member_int_types(get_int_types<N> t) {
470 static_assert( N <= max_int_width,
471 "Integer width must be no more than "
473 " bits."
474 );
475 using T = decltype(t);
476
477 if constexpr ( member_type_access<utype, T>::has_member ) {
478 using I = typename T::unsigned_type;
479 using limits = std::numeric_limits<I>;
480 constexpr unsigned bits = limits::digits;
481 static_assert( limits::radix == 2 );
482 static_assert( limits::is_integer );
483 static_assert( ! limits::is_signed );
484 static_assert( bits == N );
485 }
486
487 if constexpr ( member_type_access<stype, T>::has_member ) {
488 using I = typename T::signed_type;
489 using limits = std::numeric_limits<I>;
490 constexpr unsigned bits = limits::digits + 1; // Sign bit.
491 static_assert( limits::radix == 2 );
492 static_assert( limits::is_integer );
493 static_assert( limits::is_signed );
494 static_assert( bits == N );
495 }
496
497 return true;
498}
499
500
501
502
507template<unsigned N> struct get_int_types : public ::ugmisc::get_int_types<N> {
508private:
509 static constexpr bool _check =
510 check_member_int_types(::ugmisc::get_int_types<N>{});
511};
512
513
514
515
516} /* intfind_ (::ugmisc::intfind_) */
518// INTERNAL
519
520
521
522
536template<unsigned N, int_sign Sign>
537inline constexpr bool has_int_type =
538 Sign == UNSIGNED
541 ;
542
543
545template<unsigned N> inline constexpr bool has_unsigned_type =
547
549template<unsigned N> inline constexpr bool has_signed_type = has_int_type<N, SIGNED>;
550
552template<unsigned N> inline constexpr bool has_both_int_types =
554
556template<unsigned N> inline constexpr bool has_either_int_type =
558
559
560
561
563template<unsigned N> using exact_unsigned_type = typename intfind_::get_int_types<N>::unsigned_type;
564
566template<unsigned N> using exact_signed_type = typename intfind_::get_int_types<N>::signed_type;
567
568
569
570
572namespace intfind_ {
573
574
580template<unsigned N, int_sign S> struct get_exact_int_type;
581
582
583template<unsigned N>
584struct get_exact_int_type<N, UNSIGNED> {
585 using type = typename get_int_types<N>::unsigned_type;
586};
587
588template<unsigned N>
589struct get_exact_int_type<N, SIGNED> {
590 using type = typename get_int_types<N>::signed_type;
591};
592
593
594} /* intfind_ */
596// INTERNAL
597
598
602template<unsigned N, int_sign S> using exact_int_type =
603 typename intfind_::get_exact_int_type<N, S>::type;
604
605
606
607
609namespace intfind_ {
610
611
612
613
614static constexpr unsigned find_inner_loop_max = 100;
615
616
617
618
622template<int_sign Sign>
623struct type_finder {
624 template<unsigned V>
625 constexpr decltype(auto) operator() (
626 std::integral_constant<unsigned, V> I
627 ) const
628 {
629 if constexpr ( has_int_type<I.value, Sign> ) {
630 return exact_int_type<I.value, Sign>{1};
631 } else {
632 return template_find_continue;
633 }
634 }
635};
636
637
638
639
640}
642// INTERNAL
643
644
645
646
654template<unsigned N, int_sign Sign>
656 decltype(
657 intfind_::template_callable_find<N, max_int_width, intfind_::type_finder<Sign>>()
658 );
659
660
666template<unsigned N, int_sign Sign>
668 intfind_::template_callable_find_success<N, max_int_width, intfind_::type_finder<Sign>>();
669
670
674template<unsigned N>
676
677
681template<unsigned N>
683
684
685
686
706#ifdef UGMISC_USE_CONCEPT_DECLS
707template< bool IncludingSignBit, Int T >
708constexpr unsigned signed_bitwidth(T v)
709#else
710template< bool IncludingSignBit, class T >
712#endif
713{
715 constexpr bool use_sign_bit = std::is_signed_v<T> && IncludingSignBit;
716 constexpr unsigned extra_bits = use_sign_bit ? 1 : 0;
717 const U w = v >= 0 ? (U)v : ~(U)v;
718 return bitwidth(w) + extra_bits;
719}
720
721
722
723
725namespace intfind_ {
726
727
728
729
730/*
731 * Returns `int_type{1}` if there is such a type, otherwise returns `false`.
732 */
733template<unsigned LeastBits, int_sign Sign>
734constexpr auto find_type() {
735 if constexpr ( has_sufficient_int_type<LeastBits, Sign> ) {
736 using int_type = least_int_type<LeastBits, Sign>;
737 return int_type{1};
738 } else {
739 return false;
740 }
741}
742
743
744
745
746template<class T>
747struct range_result_compare {
748 const T value;
749 range_result_compare(const range_result_compare&) = default;
750 constexpr range_result_compare(T v) : value(v) {}
751 constexpr bool valid() const { return value; }
752 constexpr unsigned size() const { return sizeof(T); }
753 constexpr unsigned used_bits() const {
754 unsigned sign_bit = std::numeric_limits<T>::is_signed ? 1 : 0;
755 return std::numeric_limits<T>::digits + sign_bit;
756 }
757};
758
759
760
761
762/*
763 * When looking for a smaller result type, a valid one always wins if the other
764 * is invalid. We care most about storage size, then used bits.
765 */
766template<class T, class U>
767constexpr bool operator < (range_result_compare<T> a, range_result_compare<U> b) {
768 if ( !a.valid() ) { return false; }
769 if ( !b.valid() ) { return true; }
770 if ( a.size() < b.size() ) { return true; }
771 if ( a.size() > b.size() ) { return false; }
772 return a.used_bits() < b.used_bits();
773}
774
775
776
777
778template<auto V, auto...SelectArgs>
779constexpr auto as_least_required_type() {
780 constexpr auto S = intfind_::combine_flags_opts<SelectArgs...>();
781 constexpr int_sign pref_sign = sign(S, V);
782 constexpr int_sign other_sign = flip(pref_sign);
783 constexpr int_sign value_sign = V < 0 ? SIGNED : UNSIGNED;
784
785 constexpr bool may_be_unsigned =
786 value_sign == UNSIGNED
787 || allow_value_sign_loss(S)
788 ;
789
790 constexpr unsigned value_bits = signed_bitwidth<true>(V);
791 constexpr bool choose_smaller = smallest(S);
792
793 // pref_value is a value which converts to true in boolean context if a
794 // type was found, and whose type (in that case) is the type found.
795 // Otherwise it will be false, and its type will be bool.
796 constexpr auto pref_value =
797 intfind_::find_type<value_bits, pref_sign>();
798 constexpr auto other_value =
799 intfind_::find_type<value_bits, other_sign>();
800
801 using pref_t = decltype(pref_value);
802 using other_t = decltype(other_value);
803
804 constexpr bool pref_valid = pref_value && (std::numeric_limits<pref_t>::is_signed || may_be_unsigned);
805
806 /*
807 * We never treat the sign select option as a mere suggestion unless
808 * allowing a smaller type of the other sign to be chosen.
809 */
810 constexpr bool other_valid = other_value && (std::numeric_limits<other_t>::is_signed || may_be_unsigned) && choose_smaller;
811
812 static_assert(
813 pref_valid || other_valid,
814 "least_required_type could not find a type that satisfies all "
815 "requirements."
816 );
817
818
819 constexpr bool use_pref = [=]() -> bool {
820 // Find out whether to use the found type (if any) of the preferred
821 // sign, or the other one. Usually there will always be a pair of
822 // types of one size available, signed and unsigned, so most of these
823 // cases will not be matched.
824 if constexpr ( ! other_valid ) {
825 // We have a valid type of preferred sign, and no type of other sign.
826 return true;
827 } else if constexpr ( ! pref_valid ) {
828 // No preferred sign available, but the other sign is available.
829 return false;
830 } else if constexpr (
831 choose_smaller && other_valid
832 &&
833 range_result_compare{other_value} < range_result_compare{pref_value}
834 )
835 {
836 // There is a smaller type available that is large enough but not
837 // of the preferred type.
838 return false;
839 } else {
840 // Special cases have been exhausted (probably not really, sorry).
841 return true;
842 }
843 }();
844
845 if constexpr ( use_pref ) {
846 static_assert(
847 pref_valid,
848 "This is Larry's fault."
849 " An earlier static assertion should have failed already."
850 );
851 return static_cast<pref_t>(V);
852 } else {
853 static_assert(
854 other_valid,
855 "This is Larry's fault."
856 " An earlier static assertion should have failed already."
857 );
858 return static_cast<other_t>(V);
859 }
860}
861
862
863
864
865} /* intfind_ (::ugmisc::intfind_) */
867// INTERNAL
868
869
870
871
886template<auto V, auto...S>
887inline constexpr auto as_least_required_int_type =
888 intfind_::as_least_required_type<V, S...>();
889
890
891
892
901template<auto V, auto...S>
903
904
905
906
907} /* ugmisc */
908#endif /* UGMISC_INT_FINDER_HPP */
Provides constexpr bit counting functions.
constexpr auto bitwidth(T) noexcept -> UGMISC_BITWISE_UINT_RETURN(T, int)
Definition bitops.hpp:317
Feature detection.
sign_opt
Definition int_finder.hpp:81
@ SELECT_VALUE_SIGN
Select a signed type iff the value is negative.
Definition int_finder.hpp:87
@ SELECT_TYPE_SIGN
Definition int_finder.hpp:85
constexpr bool has_unsigned_type
Definition int_finder.hpp:545
decltype(as_least_required_int_type< V, S... >) least_required_int_type
Definition int_finder.hpp:902
constexpr unsigned signed_bitwidth(T v)
Definition int_finder.hpp:708
#define UGMISC_INT_FINDER_MAX_INT_WIDTH
Definition int_finder.hpp:386
constexpr bool has_either_int_type
Definition int_finder.hpp:556
sign_flag
Definition int_finder.hpp:138
@ SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS
Definition int_finder.hpp:156
@ SELECT_SIGN_SMALLEST
Definition int_finder.hpp:149
least_int_type< N, UNSIGNED > least_unsigned_type
Definition int_finder.hpp:675
typename intfind_::get_int_types< N >::signed_type exact_signed_type
Definition int_finder.hpp:566
least_int_type< N, SIGNED > least_signed_type
Definition int_finder.hpp:682
constexpr bool has_sufficient_int_type
Definition int_finder.hpp:667
typename intfind_::get_int_types< N >::unsigned_type exact_unsigned_type
Definition int_finder.hpp:563
decltype( intfind_::template_callable_find< N, max_int_width, intfind_::type_finder< Sign > >()) least_int_type
Definition int_finder.hpp:655
int_sign
Used to select signed or unsigned types.
Definition int_finder.hpp:64
constexpr auto as_least_required_int_type
Definition int_finder.hpp:887
constexpr bool has_int_type
Definition int_finder.hpp:537
constexpr bool has_both_int_types
Definition int_finder.hpp:552
typename intfind_::get_exact_int_type< N, S >::type exact_int_type
Definition int_finder.hpp:602
constexpr bool has_signed_type
Definition int_finder.hpp:549
constexpr unsigned max_int_width
Definition int_finder.hpp:393
Testing for and using named static and non static members of types.
#define UGMISC_DECL_MEMBER_ACCESS(TNAME, NAME)
Definition member.hpp:148
#define UGMISC_QQ(...)
Definition quote.hpp:34
std::enable_if_t< std::numeric_limits< T >::is_integer, R > int_only
Definition sfinae_helpers.hpp:212
Definition int_finder.hpp:407
Definition member.hpp:1459