You can hide the duplication of l<x && x<h in a macro or inline function, but I have found that it is rarely worth what is not read as the Python syntax l<x<h , and quickly gets out of control when you start have macros for all the possibilities of including restrictions. Either you end up with a ridiculously long naming between_inc_inc ( between_inc_inc , between_inc_exc , ... which causes a defeat, rejecting the check in the first place), or you leave the reader to think about your range checks (" between(i, 50, 100) ... This range [,) a [,] one? (checks the code) nope it a (,) "), which is terrible if you are hunting for individual errors.
OTOH, I, as you know, abuse the "one-letter macros", which I determine exactly where and how they are needed, and undefined immediately after. Although they may seem ugly, the fact is that they are extremely local and do exactly what needs to be done, so there is no time to waste time looking for them, there are no mysterious parameters, and they can expose the bulk of the repeated calculations.
In your case, if the list is significantly long, I can do
#define B(l, h) ((l)<i) && (i<(h)) || if(B(25,50) B(100,200) B(220, 240) 0) ... #undef B
(never do this in the header!)
What is a good readability improvement instead is to use character literals instead of ASCII numbers: for example, if you want to use the range az, do 'a'<=i && i<='z' .
It seems you want to exclude alphabetic and non-printing characters: you can do this with
if((' '<=i && i<'A') || (i>'Z' && i<'a') || ('z'<i && i<=126))