Why is the ternary operator used to define 1 and 0 in a macro?
Solution 1
You are correct, in C it is tautologous. Both your particular ternary conditional and (1 > 0)
are of type int
.
But it would matter in C++ though, in some curious corner cases (e.g. as parameters to overloaded functions), since your ternary conditional expression is of type int
, whereas (1 > 0)
is of type bool
.
My guess is that the author has put some thought into this, with an eye to preserving C++ compatibility.
Solution 2
There are linting tools that are of the opinion that the result of a comparison is boolean, and can't be used directly in arithmetic.
Not to name names or point any fingers, but PC-lint is such a linting tool.
I'm not saying they're right, but it's a possible explanation to why the code was written like that.
Solution 3
You'll sometimes see this in very old code, from before there was a C standard to spell out that (x > y)
evaluates to numeric 1 or 0; some CPUs would rather make that evaluate to −1 or 0 instead, and some very old compilers may have just followed along, so some programmers felt they needed the extra defensiveness.
You'll sometimes also see this because similar expressions don't necessarily evaluate to numeric 1 or 0. For instance, in
#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) ? 1 : 0)
the inner &
-expression evaluates to either 0 or the numeric value of F_DO_GRENFELZ
, which is probably not 1, so the ? 1 : 0
serves to canonicalize it. I personally think it's clearer to write that as
#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) != 0)
but reasonable people can disagree. If you had a whole bunch of these in a row, testing different kinds of expressions, someone might've decided that it was more maintainable to put ? 1 : 0
on the end of all of them than to worry about which ones actually needed it.
Solution 4
There's a bug in the SDK code, and the ternary was probably a kludge to fix it.
Being a macro the arguments (alpha_char) can be any expression and should be parenthesized because expressions such as 'A' && 'c' will fail the test.
#define IS_LOWER( x ) ( ( (x >= 'a') && (x <= 'z') ) ? 1 : 0 )
std::cout << IS_LOWER('A' && 'c');
**1**
std::cout << IS_LOWER('c' && 'A');
**0**
This is why one should always parenthesize macro arguments in the expansion.
So in your example (but with parameters), these are both bugged.
#define FOO(x) (x > 0)
#define BAR(x) ((x > 0) ? 1 : 0)
They would most correctly be replaced by
#define BIM(x) ((x) > 0)
@CiaPan Makes a great point in following comment which is that using a parameter more than once leads to undefinable results. For instance
#define IS_LOWER( x ) (((x) >= 'a') && ((x) <= 'z'))
char ch = 'y';
std::cout << IS_LOWER(ch++);
**1**
**BUT ch is now '{'**
Solution 5
In C it doesn't matter.
Boolean expressions in C have type int
and a value that's either 0
or 1
, so
ConditionalExpr ? 1 : 0
has no effect.
In C++, it's effectively a cast to int
, because conditional expressions in C++ have type bool
.
#include <stdio.h>
#include <stdbool.h>
#ifndef __cplusplus
#define print_type(X) _Generic(X, int: puts("int"), bool: puts("bool") );
#else
template<class T>
int print_type(T const& x);
template<> int print_type<>(int const& x) { return puts("int"); }
template<> int print_type<>(bool const& x) { return puts("bool"); }
#endif
int main()
{
print_type(1);
print_type(1 > 0);
print_type(1 > 0 ? 1 : 0);
/*c++ output:
int
int
int
cc output:
int
bool
int
*/
}
It's also possible no effect was intended, and the author simply thought it made the code clearer.
Related videos on Youtube
Comments
-
Viktor S about 4 years
I'm using an SDK for an embedded project. In this source code I found some code which at least I found peculiar. In many places in the SDK there is source code in this format:
#define ATCI_IS_LOWER( alpha_char ) ( ( (alpha_char >= ATCI_char_a) && (alpha_char <= ATCI_char_z) ) ? 1 : 0 ) #define ATCI_IS_UPPER( alpha_char ) ( ( (alpha_char >= ATCI_CHAR_A) && (alpha_char <= ATCI_CHAR_Z) ) ? 1 : 0 )
Does the use of the ternary operator here make any difference?
Isn't
#define FOO (1 > 0)
the same as
#define BAR ( (1 > 0) ? 1 : 0)
?
I tried evaluating it by using
printf("%d", FOO == BAR);
and get the result 1, so it seems that they are equal. Is there a reason to write the code like they did?
-
Art about 7 yearsNo, there is no reason. You are right.
-
Scott Hunter about 7 years"They" do not appear to have used a ternary operator, based on your question.
-
Lundin about 7 yearsThe smart way would be to write
(int)(something >> x) // ensure integer even in C++
. As a rule of thumb, don't write strange code without leaving a comment about why. -
stefan about 7 yearsPartially off-topic: When does the madness of using the preprocessor stop? There is potential multiple evaluation of functions involved here. Just unnecessary.
-
pipe about 7 yearsSometimes it's nice to be explicit, too. The ternary operator here makes it clear at a glance that the purpose of the macro is to return a boolean.
-
Lightness Races in Orbit about 7 years@Art: Not really.
-
jamesqf about 7 years@Lundin: But it's not strange code, it's perfectly obvious - at least to any competent C programmer - that it's returning a logical true/false value depending on whether or not the argument is upper/lower case.
-
Justin Time - Reinstate Monica about 7 yearsAt the very least, the macros should use
(alpha_char)
instead ofalpha_char
, just to make sure it doesn't break if someone tries something crazy likeATCI_IS_LOWER(true || -1)
. -
Kevin J. Chase about 7 yearsLooks like the kind of C I wrote long ago. I'd come to C from Pascal, which had a dedicated
boolean
type, so I wasted untold time changing horrors likeif (n)
toif (0 != n)
, probably adding a dubious cast "to make sure". I'm sure I bullet-proofed inequalities likeif (a < b) ...
, too. Sure it looked like Pascal'sif a < b then ...
, but I knew that C's<
wasn't aboolean
but anint
, and anint
could be almost anything! Fear leads to gold-plating, gold-plating leads to paranoia, paranoia leads to... code like that. -
Lundin about 7 years@jamesqf Implicit portability to C++ is not perfectly obvious to a C programmer, no...
-
jamesqf about 7 years@Lundin: But where does the question of C++ portability arise? The OP says it's an embedded SDK, and uses printf rather than cout in the example, so I'm guessing it's C code, no?
-
Lundin about 7 years@jamesqf Logical/comparative operators in C return
int
of value 1/0, but they returnbool
of value true/false in C++. The only sense this code does is to ensureint
even in C++. Alternatively, the programmer didn't know what they were doing, which is also a likely explanation. In pure C, this code makes as little sense as1 ? 1 : 0
. -
jamesqf about 7 years@Lundin: I really don't understand where you're coming from at all. To me, they seem like perfectly understandable ways to do tests for upper and lower case, returning a logical TRUE/FALSE value. Since it's an embedded SDK, I assume standard lib functions aren't available. And if you care to look at the definitions of the GCC islower & isupper functions (in ctype.h), these are much simpler.
-
Lundin about 7 years@jamesqf My point is that this macro can be written as
#define ATCI_IS_LOWER( alpha_char ) ( (alpha_char) >= ATCI_char_a && (alpha_char) <= ATCI_char_z )
This gives a result of1
or0
. No need for the?:
garbage at the end. (I also fixed the severe macro operator precedence bugs that the original code suffered from.) -
jamesqf about 7 years@Lundin: Sure, you can do that, but to me the intent seems less clear.
-
-
Motun about 7 yearsI thought
bool <-> int
conversions are implicit in C++ by §4.7/4 from the standard (integral conversion), so how would it matter? -
Bathsheba about 7 yearsConsider two overloads of a function
foo
, one taking aconst bool&
the other taking aconst int&
. One of them pays you, the other reformats your hard disk. You might want to make sure you're calling the correct overload in that case. -
PSkocik about 7 yearsBTW, I think C should follow suite and make boolean expressions
_Bool
, now that C has_Bool
and_Generic
. It shouldn't break much code given that all smaller types autopromote toint
in most contexts anyway. -
CiaPan about 7 yearsAnother bug is that the parameter is used twice, so an argument with side effects will lead to unpredictable results:
IS_LOWER(++ var)
may incrementvar
once or twice, additionally it may not notice and recognize lower-case'z'
ifvar
was'y'
before the macro call. That's why such macros should be avoided, or just forward the argument to a function. -
martinkunev about 7 yearswouldn't it be more obvious to handle this case by casting the result to
int
rather than by using the ternary? -
JAB about 7 years@Bathsheba While a legitimate corner case, any programmer who uses integral overloads to implement such inconsistent behavior is fully evil.
-
StackOverflowed about 7 years
Not to name names or point any fingers,
but you sort of did both, lol. -
Lightness Races in Orbit about 7 years@Motun: Equivalence and equality are not the same thing, and sometimes we care about one or the other.
-
Jason C about 7 years@martinkunev It may be more obvious to you, in which case, if you had written the code, that's what would have been done, I suppose.
-
Admin about 7 years@JAB: You don't have to be evil, you just have to make the (common) mistake of writing a piece of code that accidentally does two different things (or worse, invoke undefined behavior) depending on integral type, and have the misfortune of doing so in a place that can trigger radically different code paths.
-
DaveBensonPhillips about 7 years"the author has put some thought into this" but didn't take a few seconds to write it down in a comment :(
-
PJTraill about 7 yearsOn the whole I prefer to use
!!( expr )
to canonicalise a boolean, but I will admit that it is confusing if you are not familiar with it. -
zwol about 7 years@PJTraill Every time you put spaces on the inside of your parentheses, God kills a kitten. Please. Think of the kittens.
-
PJTraill about 7 yearsThat is the best reason I have heard not to put spaces inside brackets in a C programme.