UgMisc 0.2-110
Miscellaneous C++ header library
Loading...
Searching...
No Matches
int_finder.hpp
Go to the documentation of this file.
1/*
2 * SPDX-Licence-Identifier: MIT
3 *
4 * Copyright 2025 Larry Chips
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the “Software”), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24#ifndef UGMISC_INT_FINDER_HPP
25#define UGMISC_INT_FINDER_HPP
26
67
68#include <cstdint>
69#include <limits>
70#include <string_view>
71#include <type_traits>
72#include "ugmisc/bitops.hpp"
73#include "ugmisc/features.hpp"
74#include "ugmisc/member.hpp"
75#include "ugmisc/quote.hpp"
76
77
78namespace ugmisc {
79
80
82enum int_sign { INVALID_SIGN = 0, UNSIGNED = 1, SIGNED = 2 };
83
84
85
86
87constexpr int_sign flip( int_sign s ) {
88 return static_cast<int_sign>(((unsigned)s) ^ 3);
89}
90
91
92
93
99enum sign_opt : std::uint8_t {
100 SELECT_SIGN_OPTION_NONE = 0,
101 SELECT_UNSIGNED = 1,
102 SELECT_SIGNED = 2,
106
107 SELECT_SIGN_OPTION_MAX = SELECT_VALUE_SIGN,
108 SELECT_SIGN_OPTION_MASK = 7,
109};
110
111
112
113
114constexpr std::string_view get_name(sign_opt opt) {
115 switch( opt ) {
116 case SELECT_SIGN_OPTION_NONE:
117 return "NONE";
118 case SELECT_UNSIGNED:
119 return "SELECT_UNSIGNED";
120 case SELECT_SIGNED:
121 return "SELECT_SIGNED";
122 case SELECT_TYPE_SIGN:
123 return "SELECT_TYPE_SIGN";
124 case SELECT_VALUE_SIGN:
125 return "SELECT_VALUE_SIGN";
126 default:
127 return "INVALID";
128 }
129};
130
131
132
133
134
135template<class T>
136constexpr int_sign sign(sign_opt opt, T v) {
137
138 switch( opt ) {
139 case SELECT_UNSIGNED: return UNSIGNED;
140 case SELECT_SIGNED: return SIGNED;
141 case SELECT_TYPE_SIGN:
142 return std::is_signed_v<T> ? SIGNED : UNSIGNED;
144 return v < 0 ? SIGNED : UNSIGNED;
145 default:
146 return INVALID_SIGN;
147 }
148}
149
150
151
152
156enum sign_flag : std::uint8_t {
157 SELECT_SIGN_FLAGS_NONE,
168
175
177};
178
179
180constexpr sign_flag operator~(sign_flag f) {
181 return static_cast<sign_flag>((~(std::uint8_t)f)&(std::uint8_t)SELECT_SIGN_FLAGS_MASK);
182}
183
184
185constexpr sign_flag operator|(sign_flag a, sign_flag b) {
186 return static_cast<sign_flag>((std::uint8_t)a | (std::uint8_t)b);
187}
188
189
190constexpr bool smallest(sign_flag s) {
191 return (std::uint8_t)SELECT_SIGN_SMALLEST & (std::uint8_t)s;
192}
193
194
195constexpr bool allow_value_sign_loss(sign_flag s) {
196 return (std::uint8_t)SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS & (std::uint8_t)s;
197}
198
199
200/*
201 * This might have to be a string instead of a string_view if we add more
202 * flags, because we can't just special case every combination.
203 *
204 * Then we would have to ditch the constexpr too.
205 */
206constexpr std::string_view get_name(sign_flag flags) {
207 bool smol = smallest(flags);
208 bool signloss = allow_value_sign_loss(flags);
209 if ( smol && signloss ) {
210 return "SELECT_SIGN_SMALLEST|SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS";
211 } else if ( smol ) {
212 return "SELECT_SIGN_SMALLEST";
213 } else if ( signloss ) {
214 return "SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS";
215 } else {
216 return "NONE";
217 }
218}
219
220
221
222
224namespace intfind_ {
225
226
227
228
229template<class T>
230inline constexpr bool is_opt = std::is_convertible_v<T, sign_opt>;
231
232template<class T>
233inline constexpr bool is_flag = std::is_convertible_v<T, sign_flag>;
234
235
236
237
243template< class T >
244constexpr sign_opt sign_opt_or_not(T t) {
245 if constexpr ( is_opt<T> ) {
246 return static_cast<sign_opt>(t);
247 } else if constexpr ( is_flag<T> ) {
248 return SELECT_SIGN_OPTION_NONE;
249 } else {
250 static_assert( false );
251 }
252}
253
254
255template<class T> constexpr bool is_non_null_opt(T t) {
256 return (unsigned)sign_opt_or_not(t);
257}
258
259
260template<class T> constexpr sign_flag sign_flag_or_not(T t) {
261 if constexpr ( is_flag<T> ) {
262 return static_cast<sign_flag>(t);
263 } else if constexpr ( is_opt<T> ) {
264 return SELECT_SIGN_FLAGS_NONE;
265 } else {
266 static_assert(
267 false,
268 "sign_flag_or_not takes sign_flag and sign_opt, "
269 "and types convertible to them."
270 );
271 }
272}
273
274
275
276
282template<auto...Opts>
283constexpr sign_opt combine_sign_opts_f()
284{
285 constexpr sign_opt opt_array[sizeof...(Opts)] = { sign_opt_or_not(Opts)... };
286 sign_opt found_opt = SELECT_SIGN_OPTION_NONE;
287 for( sign_opt opt: opt_array ) {
288 if ( opt == SELECT_SIGN_OPTION_NONE ) {
289 continue;
290 }
291 if ( found_opt != SELECT_SIGN_OPTION_NONE && opt != found_opt ) {
292 return SELECT_SIGN_OPTION_NONE;
293 }
294 found_opt = opt;
295 }
296 return found_opt;
297}
298
299
300
301
302template<auto...Opts> static constexpr sign_opt combined_sign_opts
303 = combine_sign_opts_f<Opts...>();
304
305
306template<auto...Flags> static constexpr sign_flag combined_sign_flags
307 = (SELECT_SIGN_FLAGS_NONE | ... | sign_flag_or_not(Flags));
308
309
310
311
312template<auto...Opt>
313static constexpr bool valid_sign_opts = (unsigned)combined_sign_opts<Opt...>;
314
315
316
317
318template<auto...Opt>
319static constexpr bool any_sign_opts = (... || is_non_null_opt(Opt));
320
321
322
323
331template<bool HasOpt>
332class flag_opt_combo {
333 const sign_flag m_flags;
334
335public:
336 flag_opt_combo(const flag_opt_combo&) = default;
337 constexpr flag_opt_combo(sign_flag sflags) : m_flags(sflags) {}
338
339 constexpr sign_flag flags() const { return m_flags; }
340 constexpr sign_opt safe_opt() const { return SELECT_SIGN_OPTION_NONE; }
341 constexpr sign_opt opt() const {
342 static_assert(
343 false,
344 "This flag_opt_combo does not have an option."
345 );
346 return safe_opt();
347 }
348
349 constexpr operator sign_flag() const { return flags(); }
350};
351
352
353template<>
354class flag_opt_combo<true> : public flag_opt_combo<false> {
355 const sign_opt m_opt;
356
357public:
358 flag_opt_combo(const flag_opt_combo&) = default;
359 constexpr flag_opt_combo(sign_opt sopt, sign_flag sflags)
360 : flag_opt_combo<false>(sflags), m_opt(sopt)
361 {}
362
363 constexpr sign_opt opt() const { return m_opt; }
364 constexpr sign_opt safe_opt() const { return opt(); }
365
366 constexpr operator sign_opt() const { return opt(); }
367};
368
369
370
371
372template<auto...args>
373constexpr auto combine_flags_opts()
374{
375 constexpr sign_flag flags = combined_sign_flags<args...>;
376 constexpr sign_opt opt = combined_sign_opts<args...>;
377 constexpr bool valid_opts = valid_sign_opts<args...>;
378 constexpr bool any_opts = any_sign_opts<args...>;
379
380 if constexpr ( any_opts ) {
381 static_assert( valid_opts );
382 return flag_opt_combo<true>{opt, flags};
383 } else {
384 return flag_opt_combo<false>{flags};
385 }
386}
387
388
389
390
391
392} /* intfind_ */
394// INTERNAL
395
396
397
398
399#ifndef UGMISC_INT_FINDER_MAX_INT_WIDTH
404# define UGMISC_INT_FINDER_MAX_INT_WIDTH (1U << 12);
405#endif
412
413
414
415
425template<unsigned N> struct get_int_types {};
426
427
428template<> struct get_int_types<8> {
429 using unsigned_type = std::uint8_t;
430 using signed_type = std::int8_t;
431};
432
433template<> struct get_int_types<16> {
434 using unsigned_type = std::uint16_t;
435 using signed_type = std::int16_t;
436};
437
438template<> struct get_int_types<32> {
439#ifndef UGMISC_TEST_INT_FINDER_SKIP_U32
440 using unsigned_type = std::uint32_t;
441#endif
442 using signed_type = std::int32_t;
443};
444
445template<> struct get_int_types<64> {
446 using unsigned_type = std::uint64_t;
447 using signed_type = std::int64_t;
448};
449
450
451
452
454namespace intfind_ {
455
456
457
458
459constexpr unsigned round_up_to_pow2(unsigned x) {
460 unsigned hi = 1U << bitwidth(x);
461 unsigned lo = hi >> 1;
462 return x ? (lo == x ? lo : hi) : 1;
463}
464
465
466
467
468/*
469 * This allows us to test the existence of a type easily.
470 *
471 * Resulting declarations are listed below for clarity.
472 */
473UGMISC_DECL_MEMBER_ACCESS(utype, unsigned_type);
474UGMISC_DECL_MEMBER_ACCESS(stype, signed_type);
475// has_member_type_unsigned_type< T >; // T can be a type_list.
476// member_type_unsigned_type< T, D (optional) > // T can be type_list,
477 // D is a default type.
478// member_type_test_unsigned_type< T > // Usually not used directly.
479// has_member_type_signed_type< T >; // T can be a type_list.
480// member_type_signed_type< T, D (optional) > // T can be type_list,
481 // D is a default type.
482// member_type_test_signed_type< T > // Usually not used directly.
483
484
485
486template<unsigned N>
487constexpr bool check_member_int_types(get_int_types<N> t) {
488 static_assert( N <= max_int_width,
489 "Integer width must be no more than "
491 " bits."
492 );
493 using T = decltype(t);
494
495 if constexpr ( member_type_access<utype, T>::has_member ) {
496 using I = typename T::unsigned_type;
497 using limits = std::numeric_limits<I>;
498 constexpr unsigned bits = limits::digits;
499 static_assert( limits::radix == 2 );
500 static_assert( limits::is_integer );
501 static_assert( ! limits::is_signed );
502 static_assert( bits == N );
503 }
504
505 if constexpr ( member_type_access<stype, T>::has_member ) {
506 using I = typename T::signed_type;
507 using limits = std::numeric_limits<I>;
508 constexpr unsigned bits = limits::digits + 1; // Sign bit.
509 static_assert( limits::radix == 2 );
510 static_assert( limits::is_integer );
511 static_assert( limits::is_signed );
512 static_assert( bits == N );
513 }
514
515 return true;
516}
517
518
519
520
525template<unsigned N> struct get_int_types : public ::ugmisc::get_int_types<N> {
526private:
527 static constexpr bool _check =
528 check_member_int_types(::ugmisc::get_int_types<N>{});
529};
530
531
532
533
534} /* intfind_ (::ugmisc::intfind_) */
536// INTERNAL
537
538
539
540
554template<unsigned N, int_sign Sign>
555inline constexpr bool has_int_type =
556 Sign == UNSIGNED
559 ;
560
561
563template<unsigned N> inline constexpr bool has_unsigned_type =
565
567template<unsigned N> inline constexpr bool has_signed_type = has_int_type<N, SIGNED>;
568
570template<unsigned N> inline constexpr bool has_both_int_types =
572
574template<unsigned N> inline constexpr bool has_either_int_type =
576
577
578
579
581template<unsigned N> using exact_unsigned_type = typename intfind_::get_int_types<N>::unsigned_type;
582
584template<unsigned N> using exact_signed_type = typename intfind_::get_int_types<N>::signed_type;
585
586
587
588
590namespace intfind_ {
591
592
598template<unsigned N, int_sign S> struct get_exact_int_type;
599
600
601template<unsigned N>
602struct get_exact_int_type<N, UNSIGNED> {
603 using type = typename get_int_types<N>::unsigned_type;
604};
605
606template<unsigned N>
607struct get_exact_int_type<N, SIGNED> {
608 using type = typename get_int_types<N>::signed_type;
609};
610
611
612} /* intfind_ */
614// INTERNAL
615
616
620template<unsigned N, int_sign S> using exact_int_type =
621 typename intfind_::get_exact_int_type<N, S>::type;
622
623
624
625
627namespace intfind_ {
628
629
630
631
632#ifdef UGMISC_TEST_INT_FINDER_INNER_LOOP_MAX
633static constexpr unsigned find_inner_loop_max = UGMISC_TEST_INT_FINDER_INNER_LOOP_MAX;
634#else
635static constexpr unsigned find_inner_loop_max = 100;
636#endif
637
638
639
640
647template<unsigned Min, int_sign Sign, unsigned Max>
648constexpr auto find_type_100() {
649 static_assert( Max - Min < find_inner_loop_max );
650 static_assert( Max <= max_int_width );
651 static_assert( Min <= Max );
652 static_assert( Sign == UNSIGNED || Sign == SIGNED );
653
654 if constexpr ( has_int_type<Min, Sign> ) {
656 } else if constexpr ( Min == Max ) {
657 return false;
658 } else {
659 return find_type_100<Min+1, Sign, Max>();
660 }
661}
662
663
664
665
678template<unsigned Min, int_sign Sign, unsigned Max = max_int_width>
679constexpr auto find_type() {
680 static_assert(Max <= max_int_width);
681 static_assert(Max >= Min);
682 static_assert( Sign == UNSIGNED || Sign == SIGNED );
683
684 constexpr unsigned near_max = std::min(Min+find_inner_loop_max-1, Max);
685 constexpr bool last_iter = near_max == Max;
686
687 if constexpr ( find_type_100<Min, Sign, near_max>() ) {
688 return find_type_100<Min, Sign, near_max>();
689 } else if constexpr ( last_iter ) {
690 return false;
691 } else {
692 return find_type<near_max+1, Sign, Max>();
693 }
694}
695
696
697
698
699}
701// INTERNAL
702
703
704
705
712template<unsigned N, int_sign S>
714 std::enable_if_t<
715 intfind_::find_type<N, S>(),
716 decltype(intfind_::find_type<N, S>())
717 >;
718
719
723template<unsigned N>
725
726
730template<unsigned N>
732
733
734
735
755#ifdef UGMISC_USE_CONCEPT_DECLS
756template< bool IncludingSignBit, Int T >
757constexpr unsigned signed_bitwidth(T v)
758#else
759template< bool IncludingSignBit, class T >
761#endif
762{
764 constexpr bool use_sign_bit = std::is_signed_v<T> && IncludingSignBit;
765 constexpr unsigned extra_bits = use_sign_bit ? 1 : 0;
766 const U w = v >= 0 ? (U)v : ~(U)v;
767 return bitwidth(w) + extra_bits;
768}
769
770
771
772
774namespace intfind_ {
775
776
777
778
779template<class T>
780struct range_result_compare {
781 const T value;
782 range_result_compare(const range_result_compare&) = default;
783 constexpr range_result_compare(T v) : value(v) {}
784 constexpr bool valid() const { return value; }
785 constexpr unsigned size() const { return sizeof(T); }
786 constexpr unsigned used_bits() const {
787 unsigned sign_bit = std::numeric_limits<T>::is_signed ? 1 : 0;
788 return std::numeric_limits<T>::digits + sign_bit;
789 }
790};
791
792
793
794
795/*
796 * When looking for a smaller result type, a valid one always wins if the other
797 * is invalid. We care most about storage size, then used bits.
798 */
799template<class T, class U>
800constexpr bool operator < (range_result_compare<T> a, range_result_compare<U> b) {
801 if ( !a.valid() ) { return false; }
802 if ( !b.valid() ) { return true; }
803 if ( a.size() < b.size() ) { return true; }
804 if ( a.size() > b.size() ) { return false; }
805 return a.used_bits() < b.used_bits();
806}
807
808
809
810
811template<auto V, auto...SelectArgs>
812constexpr auto as_least_required_type() {
813 constexpr auto S = intfind_::combine_flags_opts<SelectArgs...>();
814 constexpr int_sign pref_sign = sign(S, V);
815 constexpr int_sign other_sign = flip(pref_sign);
816 constexpr int_sign value_sign = V < 0 ? SIGNED : UNSIGNED;
817
818 constexpr bool may_be_unsigned =
819 value_sign == UNSIGNED
820 || allow_value_sign_loss(S)
821 ;
822
823 constexpr unsigned value_bits = signed_bitwidth<true>(V);
824 constexpr bool choose_smaller = smallest(S);
825
826 // pref_value is a value which converts to true in boolean context if a
827 // type was found, and whose type (in that case) is the type found.
828 // Otherwise it will be false, and its type will be bool.
829 constexpr auto pref_value =
830 intfind_::find_type<value_bits, pref_sign>();
831 constexpr auto other_value =
832 intfind_::find_type<value_bits, other_sign>();
833
834 using pref_t = decltype(pref_value);
835 using other_t = decltype(other_value);
836
837 constexpr bool pref_valid = pref_value && (std::numeric_limits<pref_t>::is_signed || may_be_unsigned);
838
839 /*
840 * We never treat the sign select option as a mere suggestion unless
841 * allowing a smaller type of the other sign to be chosen.
842 */
843 constexpr bool other_valid = other_value && (std::numeric_limits<other_t>::is_signed || may_be_unsigned) && choose_smaller;
844
845 static_assert(
846 pref_valid || other_valid,
847 "least_required_type could not find a type that satisfies all "
848 "requirements."
849 );
850
851
852 constexpr bool use_pref = [=]() -> bool {
853 // Find out whether to use the found type (if any) of the preferred
854 // sign, or the other one. Usually there will always be a pair of
855 // types of one size available, signed and unsigned, so most of these
856 // cases will not be matched.
857 if constexpr ( ! other_valid ) {
858 // We have a valid type of preferred sign, and no type of other sign.
859 return true;
860 } else if constexpr ( ! pref_valid ) {
861 // No preferred sign available, but the other sign is available.
862 return false;
863 } else if constexpr (
864 choose_smaller && other_valid
865 &&
866 range_result_compare{other_value} < range_result_compare{pref_value}
867 )
868 {
869 // There is a smaller type available that is large enough but not
870 // of the preferred type.
871 return false;
872 } else {
873 // Special cases have been exhausted (probably not really, sorry).
874 return true;
875 }
876 }();
877
878 if constexpr ( use_pref ) {
879 static_assert(
880 pref_valid,
881 "This is Larry's fault."
882 " An earlier static assertion should have failed already."
883 );
884 return static_cast<pref_t>(V);
885 } else {
886 static_assert(
887 other_valid,
888 "This is Larry's fault."
889 " An earlier static assertion should have failed already."
890 );
891 return static_cast<other_t>(V);
892 }
893}
894
895
896
897
898} /* intfind_ (::ugmisc::intfind_) */
900// INTERNAL
901
902
903
904
919template<auto V, auto...S>
920inline constexpr auto as_least_required_int_type =
921 intfind_::as_least_required_type<V, S...>();
922
923
924
925
934template<auto V, auto...S>
936
937
938
939
940} /* ugmisc */
941#endif /* UGMISC_INT_FINDER_HPP */
Provides constexpr bit counting functions.
constexpr auto bitwidth(T) noexcept -> UGMISC_BITWISE_UINT_RETURN(T, int)
Definition bitops.hpp:330
Feature detection.
sign_opt
Definition int_finder.hpp:99
@ SELECT_VALUE_SIGN
Select a signed type iff the value is negative.
Definition int_finder.hpp:105
@ SELECT_TYPE_SIGN
Definition int_finder.hpp:103
constexpr bool has_unsigned_type
Definition int_finder.hpp:563
decltype(as_least_required_int_type< V, S... >) least_required_int_type
Definition int_finder.hpp:935
constexpr unsigned signed_bitwidth(T v)
Definition int_finder.hpp:757
#define UGMISC_INT_FINDER_MAX_INT_WIDTH
Definition int_finder.hpp:404
constexpr bool has_either_int_type
Definition int_finder.hpp:574
sign_flag
Definition int_finder.hpp:156
@ SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS
Definition int_finder.hpp:174
@ SELECT_SIGN_SMALLEST
Definition int_finder.hpp:167
least_int_type< N, UNSIGNED > least_unsigned_type
Definition int_finder.hpp:724
typename intfind_::get_int_types< N >::signed_type exact_signed_type
Definition int_finder.hpp:584
least_int_type< N, SIGNED > least_signed_type
Definition int_finder.hpp:731
typename intfind_::get_int_types< N >::unsigned_type exact_unsigned_type
Definition int_finder.hpp:581
int_sign
Used to select signed or unsigned types.
Definition int_finder.hpp:82
constexpr auto as_least_required_int_type
Definition int_finder.hpp:920
constexpr bool has_int_type
Definition int_finder.hpp:555
constexpr bool has_both_int_types
Definition int_finder.hpp:570
typename intfind_::get_exact_int_type< N, S >::type exact_int_type
Definition int_finder.hpp:620
constexpr bool has_signed_type
Definition int_finder.hpp:567
std::enable_if_t< intfind_::find_type< N, S >(), decltype(intfind_::find_type< N, S >()) > least_int_type
Definition int_finder.hpp:713
constexpr unsigned max_int_width
Definition int_finder.hpp:411
Testing for and using named static and non static members of types.
#define UGMISC_DECL_MEMBER_ACCESS(TNAME, NAME)
Definition member.hpp:166
#define UGMISC_QQ(...)
Definition quote.hpp:53
std::enable_if_t< std::numeric_limits< T >::is_integer, R > int_only
Definition sfinae_helpers.hpp:235
Definition int_finder.hpp:425
Definition member.hpp:1378