Is there a boost way to avoid this macro usage?
Hi all, I posted this question to comp.lang.c++.moderated and got a single reply that basically said "it looks like you need to use macros", but I thought I would ask the same question here, just in case you MPL folks have some tricks up your boost library sleeves. Basically, I need to make an enum and a static array of structures from one data set and I only know one way to do it, and it involves an amount of "trickiness" that some find disturbing. The original post: In the code shown below, the CODE_TABLE macro is used to insert a sequence of CODE(codeValue) macros. By changing the definition of CODE(codeValue) right before each usage of CODE_TABLE, I can create an enumeration of codeValues, then use the same CODE_TABLE definition to create a static lookup table that can be used to find information about a codeValue, given its numeric value. In this case, I am finding the name of the enumerated value. In the real application, there are a number of other fields available. (As you can probably tell, I have written a lot of C code in my time.) IMHO, the benefit of this technique is that you only maintain CODE_TABLE. All of the required code bits are generated from it. The downside is that Macros are Evil, the macro-that-is-a-list-of-macros is confusing, and you need to carefully redefine CODE(codeValue) in the proper locations. Additionally, the real application will have multiple classes that each define their own CODE_TABLE contents, so making it all work correctly is a little tricky. My question is this: Is there any method available in C++ that can let me do away with the macros while preserving the nice quality that only one data structure needs to be maintained? Something sophisticated and tricky with templates perhaps? I expect that the fact that I stringize the enum name when creating the lookup table makes a macro the only choice. But, since that is only one component in each lookup table entry in the real code, I might be willing to give it up in order to lose the macros. Here is the code example. Contents of foo.h: ----------------- #define CODE_TABLE \ CODE(FooCodeA) \ CODE(FooCodeB) #define CODE(codeValue) codeValue, class foo { public: typedef enum { // Here comes the first use of CODE_TABLE CODE_TABLE } CodesT; foo (CodesT theCode) : m_code (theCode) {} const char *lookup (void) const; const int getCode (void) const {return m_code;} private: CodesT m_code; typedef struct { CodesT theCode; const char *pTheCodeAsString; } CodeLookupTableT; static CodeLookupTableT m_lookupTable[]; }; Contents of foo.cpp: ------------------- #include "foo.h" // Now we redefine CODE so that we can initialize the // static lookup table #undef CODE #define CODE(codeValue) {codeValue, #codeValue}, foo::CodeTableLookupT foo::m_lookupTable[] = { CODE_TABLE }; // The lookup method that finds the string description // from the numeric code const char *foo::lookup (void) const { for (int i = 0; i < (sizeof m_lookupTable)/(sizeof m_lookupTable[0]); ++i) { if (m_lookupTable[i].theCode == m_code) return m_lookupTable[i].pTheCodeAsString; } return "Unknown code value"; } contents of main.cpp: ------------------- #include <iostream> #include "foo.h" using namespace std; int main (int argc, char * const argv[]) { foo myFoo (foo::FooCodeB); cout << "myFoo with code number....: " << myFoo.getCode() << endl << " looks up the code name as: " << myFoo.lookup() << endl; return 0; } Output from running the program: ------------------------------- myFoo with code number....: 1 looks up the code name as: FooCodeB Thanks, Rush
-----Original Message----- From: boost-users-bounces@lists.boost.org [mailto:boost-users-bounces@lists.boost.org] On Behalf Of Rush Manbert
I posted this question to comp.lang.c++.moderated and got a single reply that basically said "it looks like you need to use macros", but I thought I would ask the same question here, just in case you MPL folks have some tricks up your boost library sleeves.
Just so you know, many Boost libraries, including the MPL, make extensive use of macros for code generation purposes.
IMHO, the benefit of this technique is that you only maintain CODE_TABLE. All of the required code bits are generated from it. The downside is that Macros are Evil,
Macros are not evil. Some uses of them are evil; some are not.
the macro-that-is-a-list-of-macros is confusing,
It is actually fairly straightforward.
and you need to carefully redefine CODE(codeValue) in the proper locations. Additionally, the real application will have multiple classes that each define their own CODE_TABLE contents, so making it all work correctly is a little tricky.
The only thing that is potentially confusing is the #define/#undef of
CODE--whose use is hidden behind the the CODE_TABLE interface. It would be
better, IMO, to get rid of that reliance on a specific name (i.e. "CODE"). E.g.
// foo.h
#include
My question is this: Is there any method available in C++ that can let me do away with the macros while preserving the nice quality that only one data structure needs to be maintained?
Only the preprocessor can do the stringizing. You can't initialize an array statically with templates (unless you know the exact size). If that isn't an absolute requirement, you can do the rest with templates.
Something sophisticated and tricky with templates perhaps? I expect that the fact that I stringize the enum name when creating the lookup table makes a macro the only choice. But, since that is only one component in each lookup table entry in the real code, I might be willing to give it up in order to lose the macros.
There is nothing wrong with using macros for this. You could do most of the above with the template mechanism, but it might very well turn out to be more complex (and therefore, more confusing). For something like this, I'd go with the preprocessor. It is simple and direct--you have some code that needs to be written (and maintained) in accordance with a dataset, but you don't want to write the code manually (and, more importantly, maintain manually). Therefore, let the preprocessor automate the writing. It really is straightforward--the preprocessor is just writing what you otherwise would be writing by hand. Regards, Paul Mensonides
Paul Mensonides wrote:
IMHO, the benefit of this technique is that you only maintain
CODE_TABLE. All of the required code bits are generated from it. The downside is that Macros are Evil,
Macros are not evil. Some uses of them are evil; some are not.
I actually agree with you. This was posted to comp.lang.c++.moderated, and they point you to the C++ FAQ first. It states more than once that "Macros are Evil", so I thought I'd avoid getting flamed if I mentioned that. ;-)
and you need to carefully redefine CODE(codeValue) in the proper locations. Additionally, the real application will have multiple classes that each define their own CODE_TABLE contents, so making it all work correctly is a little tricky.
The only thing that is potentially confusing is the #define/#undef of CODE--whose use is hidden behind the the CODE_TABLE interface. It would be better, IMO, to get rid of that reliance on a specific name (i.e. "CODE"). E.g.
// foo.h #include
#define CODE_TABLE (FooCodeA)(FooCodeB) // ^^^^^^^^^^ // Of course, this macro definition should never propogate // out of the header unless it is prefixed by a library // namespace. I.e. non-local definitions like this one // need to be all-caps (which you have, and that's good), // but it also needs to be prefixed (by library or program) // such as "LIBNAME_CODE_TABLE". Such an unprefixed name // should absolutely never be visible outside the location // where it is defined. That truly is evil.
class foo { public: typedef enum { BOOST_PP_SEQ_ENUM(CODE_TABLE) } CodesT; // ... };
// foo.cpp #include "foo.h"
#include
#include #include #define ENTRY(r, _, i, codeValue) \ BOOST_PP_COMMA_IF(i) { codeValue, BOOST_PP_STRINGIZE(codeValue) } \ /**/
foo::CodeTableLookupT foo::m_lookupTable[] = { BOOST_PP_SEQ_FOR_EACH_I(ENTRY, ~, CODE_TABLE) }
#undef ENTRY
const char *foo::lookup (void) const { // ... }
The advantage of this is that you make what you are doing with your table of codes at particular points explicit.
That's pretty cool. I'm going to impose on your good nature to ask a follow up question here. In the real code, each CODE macro contains more entries, like so: #define CODE_TABLE \ CODE(FooCodeA, "FooCodeA short description", "SummarySelectorFooCodeA", "DetailSelectorFooCodeA") \ CODE(FooCodeB, "FooCodeB short description", "SummarySelectorFooCodeB", "DetailSelectorFooCodeB") And the structure is defined as: typedef struct { CodesT theCode; const char *pTheCodeAsString; const char *pShortDescription; const char *pSummarySelector; const char *pDetailSelector; } CodeLookupTableT; I have done no programming with the preprocessor lib, so I only have a vague idea how to extend what you did to cover this case. (I think CODE needs to define either an array or a list or a sequence or a tuple, but that's as far as I have gotten...) Would you be willing to give it a go?
There is nothing wrong with using macros for this. You could do most of the
above with the template mechanism, but it might very well turn out to be more complex (and therefore, more confusing).
I think you're right about this. Thank you for your help. - Rush
-----Original Message----- From: boost-users-bounces@lists.boost.org [mailto:boost-users-bounces@lists.boost.org] On Behalf Of Rush Manbert
Macros are not evil. Some uses of them are evil; some are not.
I actually agree with you. This was posted to comp.lang.c++.moderated, and they point you to the C++ FAQ first. It states more than once that "Macros are Evil", so I thought I'd avoid getting flamed if I mentioned that. ;-)
Yeah, I know how that goes.
That's pretty cool. I'm going to impose on your good nature to ask a follow up question here. In the real code, each CODE macro contains more entries, like so: #define CODE_TABLE \ CODE(FooCodeA, "FooCodeA short description", "SummarySelectorFooCodeA", "DetailSelectorFooCodeA") \ CODE(FooCodeB, "FooCodeB short description", "SummarySelectorFooCodeB", "DetailSelectorFooCodeB")
And the structure is defined as: typedef struct { CodesT theCode; const char *pTheCodeAsString; const char *pShortDescription; const char *pSummarySelector; const char *pDetailSelector; } CodeLookupTableT;
I have done no programming with the preprocessor lib, so I only have a vague idea how to extend what you did to cover this case. (I think CODE needs to define either an array or a list or a sequence or a tuple, but that's as far as I have gotten...) Would you be willing to give it a go?
Certainly. You just need a have a data structure of data structures. You don't
really need CODE at all.
--------------------
foo.h
--------------------
#include
Paul Mensonides wrote:
That's pretty cool. I'm going to impose on your good nature to ask a follow up question here. In the real code, each CODE macro contains more entries, like so: #define CODE_TABLE \ CODE(FooCodeA, "FooCodeA short description", "SummarySelectorFooCodeA", "DetailSelectorFooCodeA") \ CODE(FooCodeB, "FooCodeB short description", "SummarySelectorFooCodeB", "DetailSelectorFooCodeB")
And the structure is defined as: typedef struct { CodesT theCode; const char *pTheCodeAsString; const char *pShortDescription; const char *pSummarySelector; const char *pDetailSelector; } CodeLookupTableT;
I have done no programming with the preprocessor lib, so I only have a vague idea how to extend what you did to cover this case. (I think CODE needs to define either an array or a list or a sequence or a tuple, but that's as far as I have gotten...) Would you be willing to give it a go?
Certainly. You just need a have a data structure of data structures. You don't really need CODE at all.
-------------------- foo.h -------------------- #include
#include #include #define CODE_TABLE \ ( (FooCodeA, "description", "summary", "detail") ) \ ( (FooCodeB, "description", "summary", "detail") ) \ /**/
#define ENUM(r, _, i, tuple) \ BOOST_PP_COMMA_IF(i) BOOST_PP_TUPLE_ELEM(4, 0, tuple) \ /**/
class foo { public: typedef enum { BOOST_PP_SEQ_FOR_EACH_I(ENUM, ~, CODE_TABLE) }; // ... };
#undef ENUM
-------------------- foo.cpp -------------------- #include "foo.h"
#include
#include #include #define ENTRY(r, _, i, tuple) \ BOOST_PP_COMMA_IF(i) ENTRY_II tuple \ /**/ #define ENTRY_II(code, desc, summary, detail) \ { code, BOOST_PP_STRINGIZE(code), desc, summary, detail } \ /**/
foo::CodeTableLookupT foo::m_lookupTable[] = { BOOST_PP_SEQ_FOR_EACH_I(ENTRY, ~, CODE_TABLE) }
#undef ENTRY #undef ENTRY_II
Note that I'm using an extra ENTRY_II macro here to avoid five invocations of BOOST_PP_TUPLE_ELEM.
<snipped> That. Is. So. Cool! No inner macros to redefine, and you can wrap the enum declaration in a macro that takes CODE_TABLE (or whatever the developer calls it) as a parameter. Same for the array definition. This is a big improvement on the original version. Thank you for the great help. I was unfamiliar with Chaos, but the code snippets looked interesting. I found the project on SourceForge. Will there be a release sometime? Thanks again, Rush
-----Original Message----- From: boost-users-bounces@lists.boost.org [mailto:boost-users-bounces@lists.boost.org] On Behalf Of Rush Manbert
That. Is. So. Cool! No inner macros to redefine, and you can wrap the enum declaration in a macro that takes CODE_TABLE (or whatever the developer calls it) as a parameter. Same for the array definition. This is a big improvement on the original version. Thank you for the great help.
You are welcome.
I was unfamiliar with Chaos, but the code snippets looked interesting. I found the project on SourceForge. Will there be a release sometime?
Eventually. Chaos is a much more advanced version of the Boost pp-lib. However, it may not suit your needs in any case because it contains no workarounds for broken preprocessors (nor will it). This severly limits its portability. The Boost pp-lib, while inferior in every way, contains *extensive* workarounds for broken preprocessors, and it has enough power to do what people usually use preprocessor metaprogramming for. It just isn't always as pretty. Regards, Paul Mensonides
participants (2)
-
Paul Mensonides
-
Rush Manbert