First approach, if you know what standard type you have (so your type is no typedef
) go with the {U}INT_MAX
macros and check against the possible sizes.
If you don't have that, for unsigned types this is relatively easy conceptually. For your favorite type T
, just do (T)-1
and do a monster test macro that checks against all possible values with ?:
. Since these then are only compile time constant expressions, any decent compiler will optimize that out a leave you with just the value that you are interested in.
This wouldn't work in #if
etc, because of the type cast, but this can't be avoided in a simple way.
For signed types this is more complicated. For types at least as wide as int
you can hope to do a trick to promote to the corresponding unsigned type and get the width of that type then. But to know whether or not your signed type has just one value bit less or not, no I don't think that there is a generic expression to know that.
Edit: Just to illustrate this a bit, I give some extracts of what you
can do to make this approach (for unsigned types) not generate too
large expressions in P99 I have something like
#ifndef P99_HIGH2
# if P99_UINTMAX_WIDTH == 64
# define P99_HIGH2(X) \
((((X) & P00_B0) ? P00_S0 : 0u) \
| (((X) & P00_B1) ? P00_S1 : 0u) \
| (((X) & P00_B2) ? P00_S2 : 0u) \
| (((X) & P00_B3) ? P00_S3 : 0u) \
| (((X) & P00_B4) ? P00_S4 : 0u) \
| (((X) & P00_B5) ? P00_S5 : 0u))
# endif
#endif
#ifndef P99_HIGH2
# if P99_UINTMAX_WIDTH <= 128
# define P99_HIGH2(X) \
((((X) & P00_B0) ? P00_S0 : 0u) \
| (((X) & P00_B1) ? P00_S1 : 0u) \
| (((X) & P00_B2) ? P00_S2 : 0u) \
| (((X) & P00_B3) ? P00_S3 : 0u) \
| (((X) & P00_B4) ? P00_S4 : 0u) \
| (((X) & P00_B5) ? P00_S5 : 0u) \
| (((X) & P00_B6) ? P00_S6 : 0u))
# endif
#endif
where the magic constants are defined with a sequence of #if at the
beginning. There it is important to not to expose too large constants
for compilers that can't handle them.
/* The preprocessor always computes with the precision of uintmax_t */
/* so for the preprocessor this is equivalent to UINTMAX_MAX */
#define P00_UNSIGNED_MAX ~0u
#define P00_S0 0x01
#define P00_S1 0x02
#define P00_S2 0x04
#define P00_S3 0x08
#define P00_S4 0x10
#define P00_S5 0x20
#define P00_S6 0x40
/* This has to be such ugly #if/#else to ensure that the */
/* preprocessor never sees a constant that is too large. */
#ifndef P99_UINTMAX_MAX
# if P00_UNSIGNED_MAX == 0xFFFFFFFFFFFFFFFF
# define P99_UINTMAX_WIDTH 64
# define P99_UINTMAX_MAX 0xFFFFFFFFFFFFFFFFU
# define P00_B0 0xAAAAAAAAAAAAAAAAU
# define P00_B1 0xCCCCCCCCCCCCCCCCU
# define P00_B2 0xF0F0F0F0F0F0F0F0U
# define P00_B3 0xFF00FF00FF00FF00U
# define P00_B4 0xFFFF0000FFFF0000U
# define P00_B5 0xFFFFFFFF00000000U
# define P00_B6 0x0U
# endif /* P00_UNSIGNED_MAX */
#endif /* P99_UINTMAX_MAX */
#ifndef P99_UINTMAX_MAX
# if P00_UNSIGNED_MAX == 0x1FFFFFFFFFFFFFFFF
# define P99_UINTMAX_WIDTH 65
# define P99_UINTMAX_MAX 0x1FFFFFFFFFFFFFFFFU
# define P00_B0 0xAAAAAAAAAAAAAAAAU
# define P00_B1 0xCCCCCCCCCCCCCCCCU
# define P00_B2 0xF0F0F0F0F0F0F0F0U
# define P00_B3 0xFF00FF00FF00FF00U
# define P00_B4 0xFFFF0000FFFF0000U
# define P00_B5 0xFFFFFFFF00000000U
# define P00_B6 0x10000000000000000U
# endif /* P00_UNSIGNED_MAX */
#endif /* P99_UINTMAX_MAX */
.
.
.