Preprocessor conditionals *within* an expression

Hi,

I'm debating the pros and cons of embedding preprocessor conditional directives *within* expressions. Considering that the directive must begin on a (new) line, this forces the expression to span lines -- often at unnatural places.

For example:

foo = bar // not yet complete! #ifdef OPTION + baz // have to include this, as well! #endif ; // trailing statement terminator

Granted, I can cheat a little (in some cases) and hope the optimizer is smart enough for "obvious" optimizations:

foo = bar + (OPTION * baz);

but this forces OPTION to always be defined *and* means it must always have the value 0 or 1 (in this example). Failing to observe this contract causes hidden bugs.

OK, in the header defining OPTION you *could*:

#ifndef OPTION # error "Hey, you forgot to define OPTION!" #endif

#if (OPTION != 0) && (OPTION != 1) # error "Sorry, OPTION must have a value of '0' or '1'." #endif

But, there are cases where you actually want to include the "string" in your source -- or not -- based on that OPTION. E.g., an optional trailing "default" parameter override in a C++ method invocation.

The point of the question is the relative merit of deliberately breaking source lines to support conditionals.

E.g., the other way of doing this is to specify the variations of that source line in different branches of that conditional:

#ifdef OPTION foo = bar + baz; #else // OPTION foo = bar; #endif

But, this is prone to foul-up as it requires the developer to carefully craft the two (or more!) different variants of the statement. Also, it forces the reader to examine each statement and notice the differences therein (e.g., do the diff(1) in your head -- hopefully without injecting any errors in the process!)

What do other folks do in these cases?

Reply to
D Yuniskis
Loading thread data ...

There is no torture vile enough for someone who implements what you propose.

Not that I have strong opinions on this, mind you.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

*Which* "proposal"? I.e., there are times when you really *must* do different things for different "OPTIONs". Doing so at run time is inefficient so you *have* to change the code (source) that the compiler sees.

What I am looking for is the method that is least likely to result in errors in comprehension/maintenance.

E.g., the first example is a bit cumbersome to read -- but, the biggest "risk" is that dangling semicolon (which is example-dependant). And, *that* the compiler will typically catch (unless the line following also has a conspiratorial syntax error).

*ALL* stylistic issues elicit strong responses!
Reply to
D Yuniskis

The vile one, of course -- where you sometimes wrap part of a statement in an ifdef, and sometimes don't.

Seriously, I think your idea of defining the thing as 0 or 1, and possibly following that by a test, makes for more readable code.

And hyperbole aside, there are times when you have to play stupid preprocessor games. But you want to minimize them.

I once worked for a place that was getting over a principle software engineer who considered the C language to be nothing but an assembler for the C preprocessor. _All_ of the meat of her code was in defines; the actual C code just invoked macros. She would open up half a dozen blocks in one macro, then close them in another two. Her code was impossible to follow, by eye or with a debugger.

By the time I got there they had purged the code base of her code, but it was still taking your life into your own hands to propose a code macro.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

Actually, I consider them all vile -- to one degree or another. I really dislike preprocessor directives as they tend to interrupt the flow when you are reading the code. Instead of concentrating on what the code is doing, you have to momentariy step aside and figure out why it wants to "have a choice", etc.

Unfortunately, this is pretty common practice in FOSS. I would rather find a scheme that wasn't as crude yet doesn't introduce other issues.

But that doesn't address things like trailing parameters to a method invocation. I.e., in those cases where you might *want* to specify an "extra" (set of) parameter or *not*.

Yes, that's the problem. I maintain a fair bit of "portable" (loud laugh!) software that runs on different OS's, different compilers, etc. You end up spending a big part of your time keeping the preprocessor happy instead of working on the code itself. And, all the preprocessor directives start to overwhelm the code -- at times, you don't know where the "applicable code" (to whatever OS/compiler configuration you happen to be interested in at the time) starts and ends. :< (sometimes, easier to just run things through the preprocessor and let it remove all the cruft!)

Ha! Like using it to invent another language.

Use the tools/features that you need -- wisely!

Reply to
D Yuniskis

Assuming that for one machine you _always_ want the trailing parameters and for another you don't, I'd do two things:

In C++, in the class definition, I would make an inline function:

returnType thePublicFunction(wanted1, wanted2, disregard, disregard) {thePrivateFunction(wanted1, wanted2);}

It should be child's play for the optimizer to not generate extra code.

In C, in the appropriate header, I'd make a define:

#define thePublicFunction(wanted1, wanted2, disregard, disregard) \ __thePublicFunction(wanted1, wanted2)

Then in the code you just call the function with all the parameters.

That was my point. They did recover, but they had to get over the shock.

I think the real issue is that the whole damn thing is an art -- learning how to get everything parameterized in a decent way, to minimize the number of #ifdefs in the code base and to (hopefully at least) concentrate them in one well-documented place instead of having them bleed all over every file.

And because it's an art, there's no one right way to do it (except that doing it more than absolutely necessary should get you strangled).

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com
Reply to
Tim Wescott

People have been known to define a "get rid of that parameter, when needed" macro for that, like this:

#ifdef SWITCH_THAT_CAUSES_EXTRA_PARAMETERS_TO_BE_NEEDED # define EXTRA_PARAMETERS(foo,bar) ,(foo),(bar) /*this is horrible!*/ #else # define EXTRA_PARAMETERS(foo,bar) /*nothing*/ #endif

Other people have been known to complain violently about stuff like that.

And of course if all else fails and the extra arguments can be confined to the end of the argument list, one might consider a variadic function instead.

Reply to
Hans-Bernhard Bröker

The language allows preprocessor conditionals practically anywhere and users are taking full advantage.

Most optimizers will work just fine even with embedded conditionals. Optimizer constant folding and processing happens long after the original source has been expanded.

Conditional compiling for error checking can be very helpful in creating meaningful error messages especially in cases where out of range data may not be detected in any other way.

Regards,

Walter..

-- Walter Banks Byte Craft Limited

formatting link

Reply to
Walter Banks

As others have noted, there are times when hideous preprocessor guff is less bad than other options - making highly portable code is often one of them. But that sort of thing is a last resort, to be hidden away in a dark corner and surrounded by copious apologetic and explanatory comments.

In this case, you might have:

#ifdef OPTION #define optionalBaz 123 #else #define optionalBaz 0 #endif

foo = bar + optionalBaz;

Of course, your assumption that you "have to change the code source that the compiler sees" to get compile-time calculations is wrong - at least with any half-decent compiler (and I'm aware that you sometimes have to use less than half-decent compilers - so a solution that is neat, legible and efficient for most uses may not work for you).

Most compilers will work happily with "static const int" instead of "#define" for numeric constants, and will handle all the constant folding at compile time. A common alternative is to use enum constants.

The use of "static const" is more limited with earlier C standards (for example, you can't declare an array whose size is a static const), but most modern compilers will generate exactly the code you want when using them.

Similarly, many function-type macros can be replaced with "static inline" functions for greater readability and better static checking, with exactly the same code results.

There are certainly times when only a #define macro will do the job - but don't go out of your way looking for such cases, or for ways to make them messier!

It's vastly less error-prone than your suggested alternative.

Reply to
David Brown

No, thaat's the easy problem to solve:

#include "mach/specials.h"

The tougher problem -- and the one that always clutters up FOSS sources -- is when you have to *do* things differently for different configurations, options, etc. You can't (practically) wrap the "special stuff" in a macro or a function invocation without cluttering the hell out of header files and further obfuscating what is going on.

I think this may have been a throwback to pre HLL days. E.g., it was very common to rely heavily on the macro processor in old assemblers to give you constructs that the (assembly) language didn't provide. Create macros as if they were "primitives" in some bogo-language...

Years ago (decades??), I relied on this quite a bit to quickly code things like event driven applications -- make an interpreter and then create a set of simple macros that acted as "code generators" producing the necessary constants ("op codes") to feed the interpreter at run-time.

(John Astin) "But I'm feeling *much* better, now!"

Of course! Just like there are genres in music, "schools" of painting, etc.

Each client/employer/customer imposes their own requirements on their development efforts. You live within them for whatever reasons *they* might have.

Some industries are very heavily regulated. Compilers have to be validated (to ensure they produce the code that the source describes), command line options constrained (some folks refuse to allow certain optimizations as they alter the actual code that is executed), dead code removal, etc.

Each shop has their own quirks about coding "style" issues. Some have rigid guidelines intended (fantasized?) to produce "better code", etc.

If it was a science, there would be *an* "answer" (possibly more than one -- but at *least* one) instead of a bunch of "best (hopefully) practices".

I've learned not to argue with The Powers That Be -- they sign the checks! :> Often, the real reasons behind the decisions are too embarassing for them to admit (even to themselves). The sorts of things that cause people to design part numbering systems where the number *signifies* something :-/

I suspect I will do what most other FOSS have done -- use whatever suits my fancy and let others deal with it later! :>

Reply to
D Yuniskis

[...]

I try to keep complete expressions conditionally compiled, but minimize them to reduce the confusion. Of course, it depends on the actual code needed. For this particular example, I'd use

foo =3D bar; #ifdef OPTION foo +=3D baz; #endif

As long as foo isn't volatile, the compiler should be smart enough to generate optimal code. And even if it isn't, a redundant store to RAM isn't a huge optimization issue.

I'm not above something like the following, either:

if (time =3D=3D now && #ifdef FEATURE feature_state =3D=3D ready && #endif place =3D=3D here ) { do_it(); }

Regards,

-=3DDave

Reply to
Dave Hansen

In article , D Yuniskis wrote: .. }For example: } } foo = bar // not yet complete! }#ifdef OPTION } + baz // have to include this, as well! }#endif } ; // trailing statement terminator }

This might be ok if it only occurs once or twice (where the novelty of it mitigates the risk of confusing the reader).

.. or ... }#ifdef OPTION } foo = bar + baz; }#else // OPTION } foo = bar; }#endif } }But, this is prone to foul-up as it requires the developer to }carefully craft the two (or more!) different variants of the }statement.

This has a different problem in that it effectively writes the same code out twice (the addition of 'bar' to 'foo'). If maintenance results in deciding that a modification is required, it's too easy for only one version to be changed.

Assuming that this was just an example, rather than the exact difference to be expressed, if there's a significant number of such differences (and especially if they vary in different parts of the code), this might be better:

In header file:

#ifdef OPTION #define IF_OPTION(x) x #else #define IF_OPTION(x) #endif

then in the expression:

foo = bar IF_OPTION(+ baz);

This also allows usage such as: f(a, b, c IF_OPTION(, d));

but note that the naked 'x' in the expansion of IF_OPTION means that at each use you should ensure that the syntax makes it obvious that it's not a normal macro in case someone modfiying the code thinks that it will obey normal precedence rules.

In general such preprocessor trickery is best avoided - e.g. by defining constants which are zero where not applicable - and if you think such trickery is required, make sure you've fully considered other, more conventional and less confusing, methods.

Reply to
Charles Bryant

D Yuniskis skrev:

#define ADD_OPTION(OPTION,value) + (OPTION?0:value)

#define MYOPTION1 0 #define MYOPTION2 1

foo = bar ADD_OPTION(MYOPTION1,x) ADD_OPTION(MYOPTION1,x);

BR Ulf Samuelsson

Reply to
Ulf Samuelsson

Ulf Samuelsson skrev:

Too late, too quick

#define ADD_OPTION(option,value) + (option?value:0)

#define MYOPTION1 0 #define MYOPTION2 1

foo = bar ADD_OPTION(MYOPTION1,x) ADD_OPTION(MYOPTION2,y);

=>

foo = bar + (0?x:0) + (1?y:0);

=> foo = bar + y;

BR Ulf

Reply to
Ulf Samuelsson

Actually, the above most lends itself to my coding style as I typically break long expressions into multiple lines (e.g., one or two terms per line):

GrossIncome = HoursWorked * HourlyRate + LotteryWinnings + WeeklySalary * PAY_PERIODS + Commissions + DividendIncome + EarnedInterest ;

(Bad example as each of the terms are simple expressions but hopefully illustrates the approach)

Exactly. If the expression is sufficiently complex (perhaps spanning more than one line), then you force the user to proofread very carefully as the compiler can't read your mind to catch errors for you. By contrast, using the original approach, the compiler will tend to catch the simple screwups that manifest as syntax errors (e.g., forgetting terminating ';', forgetting a binary operator, *adding* an extra operator, etc.)

The problem with this last comment is it doesn't always work

*or* adds other complexity to expressions. E.g.,

#ifdef OPTION foo = bar * baz; #else foo = bar * baz * splat; #endif

I think I'll just opt for "easiest for me" and let folks p*ss and moan later -- as I often do when faced with maintaining something equally mangled :-/

Reply to
D Yuniskis

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.