C++ Macro expanding to an argument list - please help!

Started by
8 comments, last by MaulingMonkey 16 years, 10 months ago
Hi, I have the following macros that expand to argument lists for other functions: #define PANTS_COLOR 131, 87, 146, 180 #define RENDER_SELECTED_COLOR 50, 253, 212, 180 #define ADD_COLOR(W1, W2, C1, C2, C3, C4, D1, D2, D3, D4) (W1*C1+W2*D1), (W1*C2+W2*D2), (W1*C3+W2*D3), (W1*C4+W2*D4) For a function expecting 4 arguments, the following works: glColor4ub(PANTS_COLOR); But, the following does not work: glColor4ub(ADD_COLOR(0.3, 0.7, PANTS_COLOR, RENDER_SELECTED_COLOR)); The error is: error C2059: syntax error : ')' because, I guess, the ADD_COLOR macro expects 10 arguments and is expanded before its argument macros are. Is there a way to make such nested macros work?? Thanks, Masha
Advertisement
Not in a particularly pleasant or sane fashion, no.

Besides, macros are pretty evil and this is a particularly egregious use of them. There are better alternatives.

I can't say if you're using C or C++ here, but it seems like your solution is to create a Color structure that encapsulates the RGBA values and provides either methods and operator overloads (in C++) or external functions (in C) that perform operations such as "color addition."

You will not have the "luxury" of passing "single parameters" to glColor4ub() any more, you'll have to pass glColor4ub(color.R,color.G,color.B,color.A), but that is better. Using macros to change the syntax of the language or API interface is inexcusable (in the general case), especially if its just to "save you some typing."
Macro's are bad MmmmmKay.

Switch to using a const float array for the values.
Oh this is too bad. Thank you though.
I usually just use #define macros to define global constants. And in this case the only thing I must do with these color constants is add them in this way.
Quote:Oh this is too bad. Thank you though.
I usually just use #define macros to define global constants. And in this case the only thing I must do with these color constants is add them in this way.


1) You shouldnt use them for global constants either, you should use:
namespace{    const float my_float = 5.0f;}


2) Simplified version of what happens when a macro is expanded is
a) all paramaers are substitured
b) All macros in the resulting expression are expanded skipping all macros that have previously been expanded during the expansion of this macro.
BOOST_PP_EVAL would work here, but honestly, this is one of the worse abuse of macros I've seen in awhile. I'd expect something more similar to the following:

const color4ub pants_color( 131, 87, 146, 180 );const color4ub render_selected_color( 50, 253, 212, 180 );glColor( pants_color );glColor( 0.3*pants_color  +  0.7*render_selected_color );


In this case, we only need 2 major components:

1) A color4ub structure.
2) A glColor function which accepts said structure and calls the appropriate glColor**.

In my own code I have a mess of templates to handle this, since I'm a freak like that. Here's a simpler, saner implementation which should allow the above example to compile just fine:

struct color4ub {    unsigned char red, green, blue, alpha;    color4ub(): red(), green(), blue(), alpha(255) {}    color4ub( unsigned char red, unsigned char green, unsigned char blue, unsigned char alpha = 255 ): red(red), green(green), blue(blue), alpha(alpha) {}    color4ub& operator+=( const color4ub& other ) {        red   += other.red;        green += other.green;        blue  += other.blue;        alpha += other.alpha;        return *this;    }    color4ub& operator*=( double factor ) {        red   *= factor;        green *= factor;        blue  *= factor;        alpha *= factor;        return *this;    }    // We can write +/* and other binary operators in terms of +=/*=, etc    // Compiler performed Named Return Value Optimization (NRVO) works well here.    friend color4ub operator+( const color4ub& lhs, const color4ub& rhs ) {        color4ub temp = lhs;        temp += rhs;        return temp;    }    friend color4ub operator*( double factor, const color4ub& color ) {        color4ub temp = color;        temp *= factor;        return temp;    }    friend color4ub operator*( const color4ub& color, double factor ) {        color4ub temp = color;        temp *= factor;        return temp;    }    friend void glColor( const color4ub& color ) {        ::glColor4ub( color.red, color.green, color.blue, color.alpha );    }};
Thank you.
Yes, of course, macros are not a good idea for constants either due to scoping issues. On the other hand, for an application with many constants, any alternative will require a lage number of global variables to be kept in memory during program run-time. And that is entirely unnecessary, so I prefer to use macros for the purpose.
Admittedly, a user of macros is liable to fall into the temptation entitled "let's see what else a macro can do", which is apparently what I have almost done above: thanks for your forewarning.
Of course, a structure like the one you have described, is good programming, but it is far too much code for a humble purpose it is to serve in an application that does not concern itself with color manipulation, not to mention the unnecessary function call overhead.
But I guess that is the eternal tension between C and C++ programming: speed v. safety; simplicity v. many beautiful fences.
Quote:
On the other hand, for an application with many constants, any alternative will require a lage number of global variables to be kept in memory during program run-time.

You don't know that, nor do you know that will be a performance concern. This is premature micro-optimization.

Quote:
Of course, a structure like the one you have described, is good programming, but it is far too much code for a humble purpose it is to serve in an application that does not concern itself with color manipulation,

It's hardly too much code. Your program does manipulate colors, even if it only does it once.

Quote:
not to mention the unnecessary function call overhead.

More unfounded premature micro-optimization. The compiler could probably inline most of that code, and even if it didn't the "overhead" would be effectively nonexistent.

Quote:
speed v. safety; simplicity v. many beautiful fences.

In this case, since your solutions are no faster (in terms of statistical significance, but are in fact less safe and more complex due to their idiosyncrasies, it's more like "stupid programming versus smart programming."

So which will it be?

Quote:Original post by shumash
On the other hand, for an application with many constants, any alternative will require a lage number of global variables to be kept in memory during program run-time. And that is entirely unnecessary, so I prefer to use macros for the purpose.

Gosh yes, and your program would waste all of 32 byte (I see 8 integer values being defined through macros) if you used const variables instead of macros (and that's assuming the compiler didn't optimize it away)
For int constants, you could even use an enum to save those precious couple of bytes, without the problems macros bring you. ;)

If you're the type who frets over optimization all the time, it's usually a good idea to keep some idea of the magnitude of the overhead in question. Imagine how many constants you'd need to waste even 1MB of memory. (And then imagine how many #defines you'd need to avoid that)
Quote:Original post by shumash
Thank you.
Yes, of course, macros are not a good idea for constants either due to scoping issues. On the other hand, for an application with many constants, any alternative will require a lage number of global variables to be kept in memory during program run-time. And that is entirely unnecessary, so I prefer to use macros for the purpose.


Literal data requires application-wide storage just as much as globals do. In more complex situations, macros can actually worsen the situation, by forcing not just the data to be stored, but extra code to be generated by the compiler at every access of the constant in question -- or by making it harder for the compiler to inline a given function or fit it into L1 cache, for example.

Fortunately, your rationalizations for macro use here completely fall flat on their face in the presence of your modern optimizing compiler. Hell, it was a horrible rationalization even for 10 years ago, before C++ was even standardized.

&2cent;

This topic is closed to new replies.

Advertisement