ugmisc 0.2-76
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#pragma once
25
66
67#include <cstdint>
68#include <limits>
69#include <string_view>
70#include <type_traits>
71#include "ugmisc/bitops.hpp"
72#include "ugmisc/features.hpp"
73#include "ugmisc/member.hpp"
74#include "ugmisc/quote.hpp"
75
76
77namespace ugmisc {
78
79
81enum int_sign { INVALID_SIGN = 0, UNSIGNED = 1, SIGNED = 2 };
82
83
84
85
86constexpr int_sign flip( int_sign s ) {
87 return static_cast<int_sign>(((unsigned)s) ^ 3);
88}
89
90
91
92
98enum sign_opt : std::uint8_t {
99 SELECT_SIGN_OPTION_NONE = 0,
100 SELECT_UNSIGNED = 1,
101 SELECT_SIGNED = 2,
105
106 SELECT_SIGN_OPTION_MAX = SELECT_VALUE_SIGN,
107 SELECT_SIGN_OPTION_MASK = 7,
108};
109
110
111
112
113constexpr std::string_view get_name(sign_opt opt) {
114 switch( opt ) {
115 case SELECT_SIGN_OPTION_NONE:
116 return "NONE";
117 case SELECT_UNSIGNED:
118 return "SELECT_UNSIGNED";
119 case SELECT_SIGNED:
120 return "SELECT_SIGNED";
121 case SELECT_TYPE_SIGN:
122 return "SELECT_TYPE_SIGN";
123 case SELECT_VALUE_SIGN:
124 return "SELECT_VALUE_SIGN";
125 default:
126 return "INVALID";
127 }
128};
129
130
131
132
133
134template<class T>
135constexpr int_sign sign(sign_opt opt, T v) {
136
137 switch( opt ) {
138 case SELECT_UNSIGNED: return UNSIGNED;
139 case SELECT_SIGNED: return SIGNED;
140 case SELECT_TYPE_SIGN:
141 return std::is_signed_v<T> ? SIGNED : UNSIGNED;
143 return v < 0 ? SIGNED : UNSIGNED;
144 default:
145 return INVALID_SIGN;
146 }
147}
148
149
150
151
155enum sign_flag : std::uint8_t {
156 SELECT_SIGN_FLAGS_NONE,
167
174
176};
177
178
179constexpr sign_flag operator~(sign_flag f) {
180 return static_cast<sign_flag>((~(std::uint8_t)f)&(std::uint8_t)SELECT_SIGN_FLAGS_MASK);
181}
182
183
184constexpr sign_flag operator|(sign_flag a, sign_flag b) {
185 return static_cast<sign_flag>((std::uint8_t)a | (std::uint8_t)b);
186}
187
188
189constexpr bool smallest(sign_flag s) {
190 return (std::uint8_t)SELECT_SIGN_SMALLEST & (std::uint8_t)s;
191}
192
193
194constexpr bool allow_value_sign_loss(sign_flag s) {
195 return (std::uint8_t)SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS & (std::uint8_t)s;
196}
197
198
199/*
200 * This might have to be a string instead of a string_view if we add more
201 * flags, because we can't just special case every combination.
202 *
203 * Then we would have to ditch the constexpr too.
204 */
205constexpr std::string_view get_name(sign_flag flags) {
206 bool smol = smallest(flags);
207 bool signloss = allow_value_sign_loss(flags);
208 if ( smol && signloss ) {
209 return "SELECT_SIGN_SMALLEST|SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS";
210 } else if ( smol ) {
211 return "SELECT_SIGN_SMALLEST";
212 } else if ( signloss ) {
213 return "SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS";
214 } else {
215 return "NONE";
216 }
217}
218
219
220
221
223namespace intfind_ {
224
225
226
227
228template<class T>
229inline constexpr bool is_opt = std::is_convertible_v<T, sign_opt>;
230
231template<class T>
232inline constexpr bool is_flag = std::is_convertible_v<T, sign_flag>;
233
234
235
236
242template< class T >
243constexpr sign_opt sign_opt_or_not(T t) {
244 if constexpr ( is_opt<T> ) {
245 return static_cast<sign_opt>(t);
246 } else if constexpr ( is_flag<T> ) {
247 return SELECT_SIGN_OPTION_NONE;
248 } else {
249 static_assert( false );
250 }
251}
252
253
254template<class T> constexpr bool is_non_null_opt(T t) {
255 return (unsigned)sign_opt_or_not(t);
256}
257
258
259template<class T> constexpr sign_flag sign_flag_or_not(T t) {
260 if constexpr ( is_flag<T> ) {
261 return static_cast<sign_flag>(t);
262 } else if constexpr ( is_opt<T> ) {
263 return SELECT_SIGN_FLAGS_NONE;
264 } else {
265 static_assert(
266 false,
267 "sign_flag_or_not takes sign_flag and sign_opt, "
268 "and types convertible to them."
269 );
270 }
271}
272
273
274
275
281template<auto...Opts>
282constexpr sign_opt combine_sign_opts_f()
283{
284 constexpr sign_opt opt_array[sizeof...(Opts)] = { sign_opt_or_not(Opts)... };
285 sign_opt found_opt = SELECT_SIGN_OPTION_NONE;
286 for( sign_opt opt: opt_array ) {
287 if ( opt == SELECT_SIGN_OPTION_NONE ) {
288 continue;
289 }
290 if ( found_opt != SELECT_SIGN_OPTION_NONE && opt != found_opt ) {
291 return SELECT_SIGN_OPTION_NONE;
292 }
293 found_opt = opt;
294 }
295 return found_opt;
296}
297
298
299
300
301template<auto...Opts> static constexpr sign_opt combined_sign_opts
302 = combine_sign_opts_f<Opts...>();
303
304
305template<auto...Flags> static constexpr sign_flag combined_sign_flags
306 = (SELECT_SIGN_FLAGS_NONE | ... | sign_flag_or_not(Flags));
307
308
309
310
311template<auto...Opt>
312static constexpr bool valid_sign_opts = (unsigned)combined_sign_opts<Opt...>;
313
314
315
316
317template<auto...Opt>
318static constexpr bool any_sign_opts = (... || is_non_null_opt(Opt));
319
320
321
322
330template<bool HasOpt>
331class flag_opt_combo {
332 const sign_flag m_flags;
333
334public:
335 flag_opt_combo(const flag_opt_combo&) = default;
336 constexpr flag_opt_combo(sign_flag sflags) : m_flags(sflags) {}
337
338 constexpr sign_flag flags() const { return m_flags; }
339 constexpr sign_opt safe_opt() const { return SELECT_SIGN_OPTION_NONE; }
340 constexpr sign_opt opt() const {
341 static_assert(
342 false,
343 "This flag_opt_combo does not have an option."
344 );
345 return safe_opt();
346 }
347
348 constexpr operator sign_flag() const { return flags(); }
349};
350
351
352template<>
353class flag_opt_combo<true> : public flag_opt_combo<false> {
354 const sign_opt m_opt;
355
356public:
357 flag_opt_combo(const flag_opt_combo&) = default;
358 constexpr flag_opt_combo(sign_opt sopt, sign_flag sflags)
359 : flag_opt_combo<false>(sflags), m_opt(sopt)
360 {}
361
362 constexpr sign_opt opt() const { return m_opt; }
363 constexpr sign_opt safe_opt() const { return opt(); }
364
365 constexpr operator sign_opt() const { return opt(); }
366};
367
368
369
370
371template<auto...args>
372constexpr auto combine_flags_opts()
373{
374 constexpr sign_flag flags = combined_sign_flags<args...>;
375 constexpr sign_opt opt = combined_sign_opts<args...>;
376 constexpr bool valid_opts = valid_sign_opts<args...>;
377 constexpr bool any_opts = any_sign_opts<args...>;
378
379 if constexpr ( any_opts ) {
380 static_assert( valid_opts );
381 return flag_opt_combo<true>{opt, flags};
382 } else {
383 return flag_opt_combo<false>{flags};
384 }
385}
386
387
388
389
390
391} /* intfind_ */
393// INTERNAL
394
395
396
397
398#ifndef UGMISC_INT_FINDER_MAX_INT_WIDTH
403# define UGMISC_INT_FINDER_MAX_INT_WIDTH (1U << 12);
404#endif
411
412
413
414
424template<unsigned N> struct get_int_types {};
425
426
427template<> struct get_int_types<8> {
428 using unsigned_type = std::uint8_t;
429 using signed_type = std::int8_t;
430};
431
432template<> struct get_int_types<16> {
433 using unsigned_type = std::uint16_t;
434 using signed_type = std::int16_t;
435};
436
437template<> struct get_int_types<32> {
438#ifndef UGMISC_TEST_INT_FINDER_SKIP_U32
439 using unsigned_type = std::uint32_t;
440#endif
441 using signed_type = std::int32_t;
442};
443
444template<> struct get_int_types<64> {
445 using unsigned_type = std::uint64_t;
446 using signed_type = std::int64_t;
447};
448
449
450
451
453namespace intfind_ {
454
455
456
457
458constexpr unsigned round_up_to_pow2(unsigned x) {
459 unsigned hi = 1U << bitwidth(x);
460 unsigned lo = hi >> 1;
461 return x ? (lo == x ? lo : hi) : 1;
462}
463
464
465
466
467/*
468 * This allows us to test the existence of a type easily.
469 *
470 * Resulting declarations are listed below for clarity.
471 */
472UGMISC_MEMBER_TYPE_TEST(unsigned_type);
473UGMISC_MEMBER_TYPE_TEST(signed_type);
474// has_member_type_unsigned_type< T >; // T can be a type_list.
475// member_type_unsigned_type< T, D (optional) > // T can be type_list,
476 // D is a default type.
477// member_type_test_unsigned_type< T > // Usually not used directly.
478// has_member_type_signed_type< T >; // T can be a type_list.
479// member_type_signed_type< T, D (optional) > // T can be type_list,
480 // D is a default type.
481// member_type_test_signed_type< T > // Usually not used directly.
482
483
484
485template<unsigned N>
486constexpr bool check_member_int_types(get_int_types<N> t) {
487 static_assert( N <= max_int_width,
488 "Integer width must be no more than "
490 " bits."
491 );
492 using T = decltype(t);
493
494 if constexpr ( has_member_type_unsigned_type<T> ) {
495 using I = typename T::unsigned_type;
496 using limits = std::numeric_limits<I>;
497 constexpr unsigned bits = limits::digits;
498 static_assert( limits::radix == 2 );
499 static_assert( limits::is_integer );
500 static_assert( ! limits::is_signed );
501 static_assert( bits == N );
502 }
503
504 if constexpr ( has_member_type_signed_type<T> ) {
505 using I = typename T::signed_type;
506 using limits = std::numeric_limits<I>;
507 constexpr unsigned bits = limits::digits + 1; // Sign bit.
508 static_assert( limits::radix == 2 );
509 static_assert( limits::is_integer );
510 static_assert( limits::is_signed );
511 static_assert( bits == N );
512 }
513
514 return true;
515}
516
517
518
519
524template<unsigned N> struct get_int_types : public ::ugmisc::get_int_types<N> {
525private:
526 static constexpr bool _check =
527 check_member_int_types(::ugmisc::get_int_types<N>{});
528};
529
530
531
532
533} /* intfind_ (::ugmisc::intfind_) */
535// INTERNAL
536
537
538
539
553template<unsigned N, int_sign Sign>
554inline constexpr bool has_int_type =
555 Sign == UNSIGNED
556 ? intfind_::has_member_type_unsigned_type< intfind_::get_int_types<N> >
557 : intfind_::has_member_type_signed_type< intfind_::get_int_types<N> >
558 ;
559
560
562template<unsigned N> inline constexpr bool has_unsigned_type =
564
566template<unsigned N> inline constexpr bool has_signed_type = has_int_type<N, SIGNED>;
567
569template<unsigned N> inline constexpr bool has_both_int_types =
571
573template<unsigned N> inline constexpr bool has_either_int_type =
575
576
577
578
580template<unsigned N> using exact_unsigned_type = typename intfind_::get_int_types<N>::unsigned_type;
581
583template<unsigned N> using exact_signed_type = typename intfind_::get_int_types<N>::signed_type;
584
585
586
587
589namespace intfind_ {
590
591
597template<unsigned N, int_sign S> struct get_exact_int_type;
598
599
600template<unsigned N>
601struct get_exact_int_type<N, UNSIGNED> {
602 using type = typename get_int_types<N>::unsigned_type;
603};
604
605template<unsigned N>
606struct get_exact_int_type<N, SIGNED> {
607 using type = typename get_int_types<N>::signed_type;
608};
609
610
611} /* intfind_ */
613// INTERNAL
614
615
619template<unsigned N, int_sign S> using exact_int_type =
620 typename intfind_::get_exact_int_type<N, S>::type;
621
622
623
624
626namespace intfind_ {
627
628
629
630
631#ifdef UGMISC_TEST_INT_FINDER_INNER_LOOP_MAX
632static constexpr unsigned find_inner_loop_max = UGMISC_TEST_INT_FINDER_INNER_LOOP_MAX;
633#else
634static constexpr unsigned find_inner_loop_max = 100;
635#endif
636
637
638
639
646template<unsigned Min, int_sign Sign, unsigned Max>
647constexpr auto find_type_100() {
648 static_assert( Max - Min < find_inner_loop_max );
649 static_assert( Max <= max_int_width );
650 static_assert( Min <= Max );
651 static_assert( Sign == UNSIGNED || Sign == SIGNED );
652
653 if constexpr ( has_int_type<Min, Sign> ) {
655 } else if constexpr ( Min == Max ) {
656 return false;
657 } else {
658 return find_type_100<Min+1, Sign, Max>();
659 }
660}
661
662
663
664
677template<unsigned Min, int_sign Sign, unsigned Max = max_int_width>
678constexpr auto find_type() {
679 static_assert(Max <= max_int_width);
680 static_assert(Max >= Min);
681 static_assert( Sign == UNSIGNED || Sign == SIGNED );
682
683 constexpr unsigned near_max = std::min(Min+find_inner_loop_max-1, Max);
684 constexpr bool last_iter = near_max == Max;
685
686 if constexpr ( find_type_100<Min, Sign, near_max>() ) {
687 return find_type_100<Min, Sign, near_max>();
688 } else if constexpr ( last_iter ) {
689 return false;
690 } else {
691 return find_type<near_max+1, Sign, Max>();
692 }
693}
694
695
696
697
698}
700// INTERNAL
701
702
703
704
711template<unsigned N, int_sign S>
713 std::enable_if_t<
714 intfind_::find_type<N, S>(),
715 decltype(intfind_::find_type<N, S>())
716 >;
717
718
722template<unsigned N>
724
725
729template<unsigned N>
731
732
733
734
754template< bool IncludingSignBit, class T >
757 constexpr bool use_sign_bit = std::is_signed_v<T> && IncludingSignBit;
758 constexpr unsigned extra_bits = use_sign_bit ? 1 : 0;
759 const U w = v >= 0 ? (U)v : ~(U)v;
760 return bitwidth(w) + extra_bits;
761}
762
763
764
765
767namespace intfind_ {
768
769
770
771
772template<class T>
773struct range_result_compare {
774 const T value;
775 range_result_compare(const range_result_compare&) = default;
776 constexpr range_result_compare(T v) : value(v) {}
777 constexpr bool valid() const { return value; }
778 constexpr unsigned size() const { return sizeof(T); }
779 constexpr unsigned used_bits() const {
780 unsigned sign_bit = std::numeric_limits<T>::is_signed ? 1 : 0;
781 return std::numeric_limits<T>::digits + sign_bit;
782 }
783};
784
785
786
787
788/*
789 * When looking for a smaller result type, a valid one always wins if the other
790 * is invalid. We care most about storage size, then used bits.
791 */
792template<class T, class U>
793constexpr bool operator < (range_result_compare<T> a, range_result_compare<U> b) {
794 if ( !a.valid() ) { return false; }
795 if ( !b.valid() ) { return true; }
796 if ( a.size() < b.size() ) { return true; }
797 if ( a.size() > b.size() ) { return false; }
798 return a.used_bits() < b.used_bits();
799}
800
801
802
803
804template<auto V, auto...SelectArgs>
805constexpr auto as_least_required_type() {
806 constexpr auto S = intfind_::combine_flags_opts<SelectArgs...>();
807 constexpr int_sign pref_sign = sign(S, V);
808 constexpr int_sign other_sign = flip(pref_sign);
809 constexpr int_sign value_sign = V < 0 ? SIGNED : UNSIGNED;
810
811 constexpr bool may_be_unsigned =
812 value_sign == UNSIGNED
813 || allow_value_sign_loss(S)
814 ;
815
816 constexpr unsigned value_bits = signed_bitwidth<true>(V);
817 constexpr bool choose_smaller = smallest(S);
818
819 // pref_value is a value which converts to true in boolean context if a
820 // type was found, and whose type (in that case) is the type found.
821 // Otherwise it will be false, and its type will be bool.
822 constexpr auto pref_value =
823 intfind_::find_type<value_bits, pref_sign>();
824 constexpr auto other_value =
825 intfind_::find_type<value_bits, other_sign>();
826
827 using pref_t = decltype(pref_value);
828 using other_t = decltype(other_value);
829
830 constexpr bool pref_valid = pref_value && (std::numeric_limits<pref_t>::is_signed || may_be_unsigned);
831
832 /*
833 * We never treat the sign select option as a mere suggestion unless
834 * allowing a smaller type of the other sign to be chosen.
835 */
836 constexpr bool other_valid = other_value && (std::numeric_limits<other_t>::is_signed || may_be_unsigned) && choose_smaller;
837
838 static_assert(
839 pref_valid || other_valid,
840 "least_required_type could not find a type that satisfies all "
841 "requirements."
842 );
843
844
845 constexpr bool use_pref = [=]() -> bool {
846 // Find out whether to use the found type (if any) of the preferred
847 // sign, or the other one. Usually there will always be a pair of
848 // types of one size available, signed and unsigned, so most of these
849 // cases will not be matched.
850 if constexpr ( ! other_valid ) {
851 // We have a valid type of preferred sign, and no type of other sign.
852 return true;
853 } else if constexpr ( ! pref_valid ) {
854 // No preferred sign available, but the other sign is available.
855 return false;
856 } else if constexpr (
857 choose_smaller && other_valid
858 &&
859 range_result_compare{other_value} < range_result_compare{pref_value}
860 )
861 {
862 // There is a smaller type available that is large enough but not
863 // of the preferred type.
864 return false;
865 } else {
866 // Special cases have been exhausted (probably not really, sorry).
867 return true;
868 }
869 }();
870
871 if constexpr ( use_pref ) {
872 static_assert(
873 pref_valid,
874 "This is Larry's fault."
875 " An earlier static assertion should have failed already."
876 );
877 return static_cast<pref_t>(V);
878 } else {
879 static_assert(
880 other_valid,
881 "This is Larry's fault."
882 " An earlier static assertion should have failed already."
883 );
884 return static_cast<other_t>(V);
885 }
886}
887
888
889
890
891} /* intfind_ (::ugmisc::intfind_) */
893// INTERNAL
894
895
896
897
912template<auto V, auto...S>
913inline constexpr auto as_least_required_int_type =
914 intfind_::as_least_required_type<V, S...>();
915
916
917
918
927template<auto V, auto...S>
929
930
931
932
933} /* ugmisc */
Provides constexpr bit counting functions.
constexpr bitwise_uint_only< T > bitwidth(T) noexcept
Definition bitops.hpp:235
Feature detection.
sign_opt
Definition int_finder.hpp:98
@ SELECT_VALUE_SIGN
Select a signed type iff the value is negative.
Definition int_finder.hpp:104
@ SELECT_TYPE_SIGN
Definition int_finder.hpp:102
typename intfind_::get_exact_int_type< N, S >::type exact_int_type
Definition int_finder.hpp:619
std::enable_if_t< intfind_::find_type< N, S >(), decltype(intfind_::find_type< N, S >()) > least_int_type
Definition int_finder.hpp:712
constexpr bool has_unsigned_type
Definition int_finder.hpp:562
decltype(as_least_required_int_type< V, S... >) least_required_int_type
Definition int_finder.hpp:928
#define UGMISC_INT_FINDER_MAX_INT_WIDTH
Definition int_finder.hpp:403
constexpr bool has_either_int_type
Definition int_finder.hpp:573
sign_flag
Definition int_finder.hpp:155
@ SELECT_SIGN_ALLOW_VALUE_SIGN_LOSS
Definition int_finder.hpp:173
@ SELECT_SIGN_SMALLEST
Definition int_finder.hpp:166
least_int_type< N, UNSIGNED > least_unsigned_type
Definition int_finder.hpp:723
typename intfind_::get_int_types< N >::signed_type exact_signed_type
Definition int_finder.hpp:583
least_int_type< N, SIGNED > least_signed_type
Definition int_finder.hpp:730
typename intfind_::get_int_types< N >::unsigned_type exact_unsigned_type
Definition int_finder.hpp:580
constexpr int_only< T, unsigned > signed_bitwidth(T v)
Definition int_finder.hpp:755
int_sign
Used to select signed or unsigned types.
Definition int_finder.hpp:81
constexpr auto as_least_required_int_type
Definition int_finder.hpp:913
constexpr bool has_int_type
Definition int_finder.hpp:554
constexpr bool has_both_int_types
Definition int_finder.hpp:569
constexpr bool has_signed_type
Definition int_finder.hpp:566
constexpr unsigned max_int_width
Definition int_finder.hpp:410
Testing for and using named static and non static members of types.
#define UGMISC_MEMBER_TYPE_TEST(NAME)
Definition member.hpp:481
#define UGMISC_QQ(...)
Definition quote.hpp:52
std::enable_if_t< std::numeric_limits< T >::is_integer, R > int_only
Definition sfinae_helpers.hpp:157
Definition int_finder.hpp:424