Why is the ternary operator used to define 1 and 0 in a macro?

10,422

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.

Share:
10,422

Related videos on Youtube

Viktor S
Author by

Viktor S

Developer focusing on embedded systems

Updated on April 05, 2020

Comments

  • Viktor S
    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
      Art about 7 years
      No, there is no reason. You are right.
    • Scott Hunter
      Scott Hunter about 7 years
      "They" do not appear to have used a ternary operator, based on your question.
    • Lundin
      Lundin about 7 years
      The 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
      stefan about 7 years
      Partially off-topic: When does the madness of using the preprocessor stop? There is potential multiple evaluation of functions involved here. Just unnecessary.
    • pipe
      pipe about 7 years
      Sometimes 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
      Lightness Races in Orbit about 7 years
      @Art: Not really.
    • jamesqf
      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
      Justin Time - Reinstate Monica about 7 years
      At the very least, the macros should use (alpha_char) instead of alpha_char, just to make sure it doesn't break if someone tries something crazy like ATCI_IS_LOWER(true || -1).
    • Kevin J. Chase
      Kevin J. Chase about 7 years
      Looks 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 like if (n) to if (0 != n), probably adding a dubious cast "to make sure". I'm sure I bullet-proofed inequalities like if (a < b) ..., too. Sure it looked like Pascal's if a < b then ..., but I knew that C's < wasn't a boolean but an int, and an int could be almost anything! Fear leads to gold-plating, gold-plating leads to paranoia, paranoia leads to... code like that.
    • Lundin
      Lundin about 7 years
      @jamesqf Implicit portability to C++ is not perfectly obvious to a C programmer, no...
    • jamesqf
      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
      Lundin about 7 years
      @jamesqf Logical/comparative operators in C return int of value 1/0, but they return bool of value true/false in C++. The only sense this code does is to ensure int 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 as 1 ? 1 : 0.
    • jamesqf
      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
      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 of 1 or 0. No need for the ?: garbage at the end. (I also fixed the severe macro operator precedence bugs that the original code suffered from.)
    • jamesqf
      jamesqf about 7 years
      @Lundin: Sure, you can do that, but to me the intent seems less clear.
  • Motun
    Motun about 7 years
    I thought bool <-> int conversions are implicit in C++ by §4.7/4 from the standard (integral conversion), so how would it matter?
  • Bathsheba
    Bathsheba about 7 years
    Consider two overloads of a function foo, one taking a const bool& the other taking a const 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
    PSkocik about 7 years
    BTW, 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 to int in most contexts anyway.
  • CiaPan
    CiaPan about 7 years
    Another bug is that the parameter is used twice, so an argument with side effects will lead to unpredictable results: IS_LOWER(++ var) may increment var once or twice, additionally it may not notice and recognize lower-case 'z' if var was 'y' before the macro call. That's why such macros should be avoided, or just forward the argument to a function.
  • martinkunev
    martinkunev about 7 years
    wouldn't it be more obvious to handle this case by casting the result to int rather than by using the ternary?
  • JAB
    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
    StackOverflowed about 7 years
    Not to name names or point any fingers, but you sort of did both, lol.
  • Lightness Races in Orbit
    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
    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
    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
    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
    PJTraill about 7 years
    On 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
    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
    PJTraill about 7 years
    That is the best reason I have heard not to put spaces inside brackets in a C programme.