79

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?

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Viktor S
  • 761
  • 1
  • 5
  • 13
  • 8
    No, there is no reason. You are right. – Art Mar 31 '17 at 11:08
  • "They" do not appear to have used a ternary operator, based on your question. – Scott Hunter Mar 31 '17 at 11:09
  • 1
    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. – Lundin Mar 31 '17 at 11:39
  • 29
    Partially off-topic: When does the madness of using the preprocessor stop? There is potential multiple evaluation of functions involved here. Just unnecessary. – stefan Mar 31 '17 at 13:29
  • 3
    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. – pipe Mar 31 '17 at 16:00
  • 1
    @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. – jamesqf Apr 01 '17 at 05:01
  • 5
    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)`](http://ideone.com/aZKneD). – Justin Time - Reinstate Monica Apr 01 '17 at 21:27
  • 5
    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. – Kevin J. Chase Apr 01 '17 at 22:06
  • 2
    @jamesqf Implicit portability to C++ is not perfectly obvious to a C programmer, no... – Lundin Apr 02 '17 at 17:06
  • @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? – jamesqf Apr 03 '17 at 17:15
  • 1
    @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`. – Lundin Apr 04 '17 at 06:29
  • @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. – jamesqf Apr 04 '17 at 19:12
  • 3
    @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.) – Lundin Apr 05 '17 at 06:53
  • 1
    @Lundin: Sure, you can do that, but to me the intent seems less clear. – jamesqf Apr 06 '17 at 04:28

7 Answers7

132

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.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 2
    I thought `bool <-> int` conversions are implicit in C++ by §4.7/4 from the standard (integral conversion), so how would it matter? – Motun Mar 31 '17 at 11:22
  • 70
    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. – Bathsheba Mar 31 '17 at 11:23
  • 3
    wouldn't it be more obvious to handle this case by casting the result to `int` rather than by using the ternary? – martinkunev Mar 31 '17 at 13:39
  • 18
    @Bathsheba While a legitimate corner case, any programmer who uses integral overloads to implement such inconsistent behavior is fully evil. – JAB Mar 31 '17 at 13:53
  • 1
    @Motun: Equivalence and equality are not the same thing, and sometimes we care about one or the other. – Lightness Races in Orbit Mar 31 '17 at 16:24
  • @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. – Jason C Apr 01 '17 at 05:11
  • 7
    @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. –  Apr 01 '17 at 05:39
  • 1
    "the author has put some thought into this" but didn't take a few seconds to write it down in a comment :( – DaveBensonPhillips Apr 02 '17 at 21:43
28

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.

unwind
  • 391,730
  • 64
  • 469
  • 606
19

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.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • 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. – PJTraill Apr 05 '17 at 17:41
  • 1
    @PJTraill Every time you put spaces on the inside of your parentheses, God kills a kitten. Please. Think of the kittens. – zwol Apr 06 '17 at 16:19
  • That is the best reason I have heard not to put spaces inside brackets in a C programme. – PJTraill Apr 06 '17 at 21:57
15

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 '{'**
Konchog
  • 1,920
  • 19
  • 23
  • 4
    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. – CiaPan Mar 31 '17 at 13:29
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.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • 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. – Petr Skocik Mar 31 '17 at 11:44
5

One simple explanation is that some people either don't understand that a condition would return the same value in C, or they think that it is cleaner to write ((a>b)?1:0).

That explains why some also use similar constructs in languages with proper booleans, which in C-syntax would be (a>b)?true:false).

This also explains why you shouldn't needlessly change this macro.

Hans Olsson
  • 11,123
  • 15
  • 38
0

Maybe, being an embedded software, would give some clues. Maybe there are many macros written using this style, to easy hint that ACTI lines use direct logic rather than Inverted logic.

J.Guarin
  • 91
  • 1
  • 3