Thoughts on local macro names

Nov 22, 2010 at 2:16am
This is something I've considered for a while, without coming to any sure conclusion.

When in header files, sometimes to save typing and/or to make code more readable it is useful to use a temporary macro. For example, I'm playing with bignums right now, and my operator < function must account for the signedness of the operands.

That is, 7 < 12 ==> true, but -7 < -12 ==> false.

Hence, a little macro is useful for making sure that the proper truth value is returned. Here is what I currently have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
bool biginteger::operator < ( const biginteger& that ) const
  {
  // If the signs are different
  // then return whether (this is negative) and (that is positive)
  if (this->sign != that.sign)
    return (this->sign and !that.sign);

  // At this point, we know that the signs are the same.
  // Since  7  <  12  is TRUE
  // and   -7  < -12  is FALSE
  // we'll use the sign of the numbers as a proxy for FALSE or TRUE
  // when we return to the caller.
  #define F( x ) (x) ? !sign : sign

  // If the number is itself
  if (this->data == that.data)
    return false;

  // (Length is a gross indicator of magnitude)
  if (this->length() != that.length())
    return F( this->length() < that.length() );

  // Find the most significant difference in the bit arrays
  std::pair <array_t::reverse_iterator, array_t::const_reverse_iterator>
  iters = std::mismatch(
    this->data->bits.rbegin(),
    this->data->bits.rend(),
    that .data->bits.rbegin()
    );

  // If the numbers were identical
  if (iters.first == this->data->bits.rend())
    return false;

  // Finally
  return F( *(iters.first) < *(iters.second) );

  #undef F
  }

Here my little macro F does the right thing depending on the sign of the numbers. (I store bignums in the traditional bit array [a deque, actually] plus sign bit, with copy on write semantics; the sign bit is not shared.)

Here's the question. A local metasyntactic variable like this should be safe. I like it because it is leaves the code nice and readable, and anyone idiot enough to #define F across files deserves to be eaten alive by the preprocessor...

That said, I previously had the macro name at DUTHOMHAS_TEMP__F (or something like that). While the likelihood of failure drops to near zero using this name, the code then becomes much less readable.


Does it really make any difference? What think/know you?

Thank you for reading.
Nov 22, 2010 at 4:40am
First thing that comes to my mind is softening the blow with an error message...

1
2
3
4
#ifdef F
#error "Don't #define F you dummy!"
#else
#define F ... 


Or if you want to be more aggressive, you could #undef F if defined.


Though really, if someone defines F as a macro in their program, I think they're likely to have bigger problems than this.

Another option is to use a #define that nobody would ever use because it would break too much code. Something like 'i' -- although that's not as clear.
Nov 22, 2010 at 5:19am
Personally, I don't find the macro any easier to understand than just coding the
ternary operator into the two return statements. In fact, it may be harder to
understand since in order to understand the return statements, one must look
elsewhere for the definition of the macro.
Nov 22, 2010 at 5:21am
how about ditching the ternary operator for the xor operator?

 
return ( this->length() < that.length() ) ^ sign;


Not exactly very clear, though. XD
Nov 22, 2010 at 5:28am
I've occasionally written professional code like that, but like you Disch, afterwards I always find it a bit confusing. Nonetheless and interesting alternative. It's not like such a function will ever have to change (be maintained)... it's either always right (no bugs) or useless. So perhaps a small comment with the xor would suffice.
Nov 22, 2010 at 8:07pm
Hmm, good thoughts.

The whole point of the macro and the comment is to explain what is happening, and leave the high-level abstraction alone.

8
9
10
11
12
13
  // At this point, we know that the signs are the same.
  // Since  7  <  12  is TRUE
  // and   -7  < -12  is FALSE
  // we'll use the sign of the numbers as a proxy for FALSE or TRUE
  // when we return to the caller.
  #define F( x ) (x) ? !sign : sign 

It is all explained nicely. Thereafter, the reader only need concern himself with the given function of the operator: is this < that?

20
21
  if (this->length() != that.length())
    return F( this->length() < that.length() );
35
36
  // Finally
  return F( *(iters.first) < *(iters.second) );

This reads and does exactly like you would expect. Is this < that? (Plus the known caveat that the sign counts.) The whole thing fits on the screen at once, including commentary.

I am still not convinced that using a function (albeit in the form of a macro) undermines the readability of the code, as much as would looking at all the extra stuff on the line to play with the sign bit (wait, why am I twiddling with the sign again?) Define once and reuse. I have learned the hard way to keep weird stuff in one spot and to simply reference it -- even if the function is a write-once kind of thing.

It is true that this may force a lookup, but in this case all the reader has to do is look up to the top of his text display where the macro is defined (and which, presuming he processes language from top to bottom like all other extant humans, he has already read).

As you may surmise, I don't like to break abstraction unless explicitly. (Especially since it overloads my brain when too many things are happening in one reading.)


But, do you think #define F ... #undef F would likely clash with anything? Is this a reasonable conclusion or is professional code too unreliable to trust that some moron hasn't got an 'F' defined across files somewhere?


Thank you for the commentary and interest. :-)
Nov 22, 2010 at 8:48pm
I could see someone doing something like:

some3Dvector F = {2, 10, 5}; //the force vector

But I don't know...you could make a private function inside bignum that performs the same operation as F if you wanted, but I don't know which is better really.
Nov 22, 2010 at 9:40pm
That wouldn't make any difference due to the locality of the macro. Macros are textual replacements -- nothing more. It is true that the CPP can do some thinking about the text it is manipulating, but its primary purpose isn't to be a compiler -- just to massage the source code for the compiler. The only thing it could collide with, besides locally-scoped words (like "sign" and "return" and the like), is another macro (macros are globally-scoped by definition) with the same name.

A private, inline-able function is a possibility, but it then puts both a physical and a conceptual distance between the two functions -- and creates the lookup problem that jsmith talked about.

Modulating the result based upon the sign is a separate but directly-related part of the function. Hence, it need not be expressed in the same breath, but it should be expressed in the same reading: within the function.
(That's how I view it, anyway.)
Nov 22, 2010 at 10:18pm
After thinking about it more, I don't think it's worth the effort to wonder whether or not someone #defined a symbol you want to use. You could spend all day worrying about name conflicts and end up nowhere.

For example in your code -- you're just as screwed if someone #defined iters as something else. Or length, or that, etc.

So should you really worry about it? What's the solution? There isn't one.

The only controllable factor here is choosing a name that's more or less likely to be taken as a #define. From my experience, #defines tend to be all caps -- so if that's your worry, the best you can do is avoid making the name all caps. Change it to f instead of F.

Since you're promptly undefing it, it won't pollute the namespace, so you don't have to worry about wrecking other code.
Nov 22, 2010 at 11:18pm
Hmm, I had tried that, but the all-caps convention got the best of me. However, I think you make a good point so instead of something uniquely mine but visually noisy (like "DUTHOMHAS__F") I'll just use "f".

Thanks!

(Also, thanks for the great discussion. I learned stuff. :-)
Topic archived. No new replies allowed.