Most of the terse versions have been covered, so I will cover the optimized cases with some helper macros to make them a little more terse.
It just so happens that if your range falls within your number of bits per long that you can combine all of your constants using a bitmask and just check that your value falls in the range and the variable's bitmask is non-zero when bitwise-anded with the constant bitmask.
/* This macro assumes the bits will fit in a long integer type,
* if it needs to be larger (64 bits on x32 etc...),
* you can change the shifted 1ULs to 1ULL or if range is > 64 bits,
* split it into multiple ranges or use SIMD
* It also assumes that a0 is the lowest and a9 is the highest,
* You may want to add compile time assert that:
* a9 (the highest value) - a0 (the lowest value) < max_bits
* and that a1-a8 fall within a0 to a9
*/
#define RANGE_TO_BITMASK_10(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9) \
(1 | (1UL<<((a1)-(a0))) | (1UL<<((a2)-(a0))) | (1UL<<((a3)-(a0))) | \
(1UL<<((a4)-(a0))) | (1UL<<((a5)-(a0))) | (1UL<<((a6)-(a0))) | \
(1UL<<((a7)-(a0))) | (1UL<<((a8)-(a0))) | (1UL<<((a9)-(a0))) )
/*static inline*/ bool checkx(int x){
const unsigned long bitmask = /* assume 64 bits */
RANGE_TO_BITMASK_10('A','B','C','F','G','H','c','f','y','z');
unsigned temp = (unsigned)x-'A';
return ( ( temp <= ('z'-'A') ) && !!( (1ULL<<temp) & bitmask ) );
}
Since all of a# values are constants, they will be combined into 1 bitmask at compile time. That leaves 1 subtraction and 1 compare for the range, 1 shift and 1 bitwise and ... unless the compiler can optimize further, it turns out clang can (it uses the bit test instruction BTQ):
checkx: # @checkx
addl $-65, %edi
cmpl $57, %edi
ja .LBB0_1
movabsq $216172936732606695, %rax # imm = 0x3000024000000E7
btq %rdi, %rax
setb %al
retq
.LBB0_1:
xorl %eax, %eax
retq
It may look like more code on the C side, but if you are looking to optimize, this looks like it may be worth it on the assembly side. I'm sure someone could get creative with the macro to make it more useful in a real programming situations than this "proof of concept".
That will get a little complex as a macro, so here is an alternative set of macros to setup a C99 lookup table.
#include <limits.h>
#define INIT_1(v,a) [ a ] = v
#define INIT_2(v,a,...) [ a ] = v, INIT_1(v, __VA_ARGS__)
#define INIT_3(v,a,...) [ a ] = v, INIT_2(v, __VA_ARGS__)
#define INIT_4(v,a,...) [ a ] = v, INIT_3(v, __VA_ARGS__)
#define INIT_5(v,a,...) [ a ] = v, INIT_4(v, __VA_ARGS__)
#define INIT_6(v,a,...) [ a ] = v, INIT_5(v, __VA_ARGS__)
#define INIT_7(v,a,...) [ a ] = v, INIT_6(v, __VA_ARGS__)
#define INIT_8(v,a,...) [ a ] = v, INIT_7(v, __VA_ARGS__)
#define INIT_9(v,a,...) [ a ] = v, INIT_8(v, __VA_ARGS__)
#define INIT_10(v,a,...) [ a ] = v, INIT_9(v, __VA_ARGS__)
#define ISANY10(x,...) ((const unsigned char[UCHAR_MAX+1]){ \
INIT_10(-1, __VA_ARGS__) \
})[x]
bool checkX(int x){
return ISANY10(x,'A','B','C','F','G','H','c','f','y','z');
}
This method will use a (typically) 256 byte table and a lookup that reduces to something like the following in gcc:
checkX:
movslq %edi, %rdi # x, x
cmpb $0, C.2.1300(%rdi) #, C.2
setne %al #, tmp93
ret
NOTE: Clang doesn't fare as well on the lookup table in this method because it sets up const tables that occur inside functions on the stack on each function call, so you would want to use INIT_10 to initialize a static const unsigned char [UCHAR_MAX+1]
outside of the function to achieve similar optimization to gcc.