Are there range operators in C like there are in, say, Swift (closed range, open range)?
For example, in a switch
statement, in Swift I would write this:
switch num {
case 144...168:
print("Something between 144 and 168")
case 120..<144:
print("Something between 120 and 143")
default: break
}
If I try something similar in C, using printf
instead of print
of course, I cannot use the same range operators. Is there something I can use instead?
Thank you
You can use a series of if
statements.
When I do range matches, I create macros to simplify the code.
And, I've found that a do { } while (0);
block can simplify things vs. a large if/else
ladder.
Here is some code:
#define RNGE(_val,_lo,_hi) \
(((_val) >= (_lo)) && ((_val) <= (_hi)))
#define RNGEM1(_val,_lo,_hi) \
(((_val) >= (_lo)) && ((_val) < (_hi)))
do {
if (RNGE(num,144,168)) {
printf("Something between 144 and 168\n");
break;
}
if (RNGEM1(num,120,144)) {
printf("Something between 120 and 143\n")
break;
}
// default ...
} while (0);
UPDATE:
Nice! Not too familiar with macros, but they look like functions. What is the backslash at the end of the line? Also, why the underscores? Convention? Preference? Second and fourth line perfectly clear! – NotationMaster
Macros are a way to "generate" code. They are [usually] processed in a separate first stage of the compiler: the C preprocessor (aka cpp
). They operate as if you "inserted" the code with an editor.
The backslashes are required if you want to define your macro on multiple lines [as I did].
We can look at the source generated by the macros with (e.g.)
cc -E -P -o foo.i foo.c
Here is the output of that stage:
do {
if ((((num) >= (144)) && ((num) <= (168)))) {
printf("Something between 144 and 168\n");
break;
}
if ((((num) >= (120)) && ((num) < (144)))) {
printf("Something between 120 and 143\n")
break;
}
} while (0);
The underscores are my personal convention (developed after 40+ years ;-).
Most people would define the macros as:
#define RNGE(num,lo,hi) \
(((num) >= (lo)) && ((num) <= (hi)))
#define RNGEM1(num,lo,hi) \
(((num) >= (lo)) && ((num) < (hi)))
I use them to distinguish the macro arguments from C symbols. Notice here I changed _val
into num
. I did that deliberately to use the same symbol your example used (i.e. num
). Now, the generated output has num
in it. But, is that num
from the [function] scoped C variable directly (e.g. I forgot to define the argument in the macro) or did it come through the argument?
This are somewhat controversial because, in C, there are standards for libraries (e.g. POSIX) that reserve all symbols that start with "_" for standard library usage.
That is just a convention, however. It's rare for a symbol I define with "_" to conflict with any internal library symbol. If one ever does, I'm perfectly willing to accept the responsibility to change my code. In fact, in 40 years, I've never had such a conflict and have never had to rework code.
For macro arguments, they are in a different namespace, so far less likely to conflict with a global variable or function name in a [POSIX] library.
Also, I consider it to be extreme hubris on the part of POSIX to co-opt such a broad mechanism for themselves. In python
, an "_" prefix is a convention that the object function is private to the object.
If you want the benefit of the "_" without the hassle of "violating" the convention (e.g):
#define RNGE(val_,lo_,hi_) \
(((val_) >= (lo_)) && ((val_) <= (hi_)))
#define RNGEM1(val_,lo_,hi_) \
(((val_) >= (lo_)) && ((val_) < (hi_)))
Macros are useful in certain situations where a function won't work. Consider the following where we replace the macro with an inline
function:
static inline int
RNGE(int val,int lo,int hi)
{
return (val >= lo) && (val <= hi);
}
// this works ...
int num;
do {
if (RNGE(num,144,168)) {
printf("Something between 144 and 168\n");
break;
}
// default ...
} while (0)
// this blows up at compile time ...
int arr[1000];
int *ptr = &arr[37];
do {
if (RNGE(ptr,&arr[144],&arr[168])) {
printf("Something between array elements 144 and 168\n");
break;
}
// default ...
} while (0)
C doesn't have overloaded functions like some languages (e.g. C++). So, we have to explicitly generate separate functions for each argument type.
In this instance, the macro works for any type that can be compared.
UPDATE #2:
For more on using do { ... } while (0)
in place of if/else
ladder logic or switch/case
see my answer: How to properly make a counting algorithm to count from file?
thank you for the extra explanation. I recall in Swift these range operators would be perfect for usage in a switch statement. Yet, here, if I use this macro in the switch, the compiler still tells "Expression is not an integer constant expression". Am I missing something here? – NotationMaster
The short answer is ...
with switch (switch_expression)
then switch_expression
can be just about anything.
But, a limitation of C with case case_expression
is that case_expression
can only be something that evaluates to a constant at compile time.
switch/case
is a [very useful] nicety. But, anything it does can be done with if/else
.
So, the way to handle this is to use the code in my first example above.
Warning: This is where you should probably stop reading ... :-)
The long answer ...
So, why does C have this restriction? Mostly historical. But, C wants to generate extremely fast code.
Many switch/case
blocks use consecutive numbers in the case
expressions:
switch (num) {
case 1:
x = 23;
break;
case 2:
y = 37;
break;
case 3:
z = 19;
break;
case ...:
...;
break;
case 100:
q = 28;
break;
default:
abort();
break;
}
The simple/obvious code would be (using if/else
ladder logic that I convert here to use my do/while/0
"trick" would be:
do {
if (num == 1) {
x = 23;
break;
}
if (num == 2) {
y = 37;
break;
}
if (num == 3) {
z = 19;
break;
}
// cases for 4-99 ...
if (num == 100) {
q = 28;
break;
}
// default:
abort();
} while (0);
However ... There's a lot of if
statements in this. But, because the case
expressions are consecutive, the optimizing compiler can detect this and generate [asm] code that does a range check (i.e. if num
is within the range 1-100
) and can use the num
value to index into a pointer table that jumps directly to the corresponding code snippet.
Here is some pseudo code that shows this:
static void *switch_vector[] = {
&&L_1,
&&L_2,
&&L_3,
...,
&&L_100
};
if (RNGE(num,1,100))
goto switch_vector[num - 1];
else
goto L_DEFAULT;
L_1:
x = 23;
goto L_ENDSWITCH;
L_2:
y = 37;
goto L_ENDSWITCH;
L_3:
z = 19;
goto L_ENDSWITCH;
// more labels ...
L_100:
q = 28;
goto L_ENDSWITCH;
L_DEFAULT:
abort();
L_ENDSWITCH:
// code after switch
Side note: I called this "pseudo" code, but this is actually valid C code using the very advanced usage of a "computed" goto
in C. Don't actually use it as it's virtually never needed. The compiler will usually generate much better code using the simpler constructs above. Remember, I did warn you :-)
See: