Preprocessor

  • Thread starter =?iso-8859-1?B?RnJhbmstUmVu6SBTY2jkZmVy?=
  • Start date
?

=?iso-8859-1?B?RnJhbmstUmVu6SBTY2jkZmVy?=

Is there a way to write code like

#include IMPLEMENT_THIS(my_class, my_member_type)

for what looks currently like

#define __C my_class
#define __MT my_member_type
#include <my_implementer.i>

where 'my_implementer.i' contains something like

struct __C { __MT obj; };
#undef __C
#undef __MT

Boost's preprocessor lib does something like this. However, I would
be grateful if I could circumvent its source code analysis and someone
could give me a hint.

Best Regards

Frank
 
P

Paul Mensonides

Frank-René Schäfer said:
Is there a way to write code like

#include IMPLEMENT_THIS(my_class, my_member_type)

for what looks currently like

#define __C my_class
#define __MT my_member_type
#include <my_implementer.i>

where 'my_implementer.i' contains something like

struct __C { __MT obj; };
#undef __C
#undef __MT

Boost's preprocessor lib does something like this. However, I would
be grateful if I could circumvent its source code analysis and someone
could give me a hint.

Well, you can't define a macro in an #include directive, so, at the very least,
you'd have to pull the arguments out into a separate "arguments" macro.

// lib/implement.hpp

#ifndef LIB_IMPLEMENT_ARGS

#ifndef LIB_IMPLEMENT_HPP
#define LIB_IMPLEMENT_HPP

#include <boost/preprocessor/seq/elem.hpp>

#define LIB_IMPLEMENT() "lib/implement.hpp"

#endif

#else

struct BOOST_PP_SEQ_ELEM(0, LIB_IMPLEMENT_ARGS) {
BOOST_PP_SEQ_ELEM(1, LIB_IMPLEMENT_ARGS) obj;
};

#undef LIB_IMPLEMENT_ARGS

#endif

// client...

#include "lib/implement.hpp"

#define LIB_IMPLEMENT_ARGS (my_class)(my_member_type)
??=include LIB_IMPLEMENT()

All of the above aside, whether or not doing something like the above is a good
design is highly dependent on what you're actually trying to accomplish. First,
in order even approach being good design, it would have to be used many times.
Second, for code generation as trivial as the above, you don't gain anything
(over just using a macro) by using file inclusion as a generation device. The
primary gain of file inclusion as a generation device is that the result is
usually more debuggable (because the generated code contains newlines). For
something as trivial as the above, you'd be better off just using a simple
macro:

#define IMPLEMENT(id, type) \
struct id { \
type obj; \
}; \
/**/

IMPLEMENT(my_class, my_member_type)

Third, if the generated code is this trivial or debuggability for the generated
code is not a major concern, and all of the uses of the generator are in the
same place (i.e. same file), then define the generator macro locally, generate
what you want, and then undefine the generator macro:

#define impl(id, type) \
struct id { \
type obj; \
}; \
/**/

impl(my_class1, my_member_type1)
impl(my_class2, my_member_type2)
impl(my_class3, my_member_type3)
impl(my_class4, my_member_type4)
impl(my_class5, my_member_type5)
// ...

#undef impl

(Rule: Though shalt use lowercase letters in a macro name if and only if the
macro is local to a file--meaning that it is defined, used, and undefined all in
the same file with no file inclusions while it is defined.)

Fourth, if there are a lot of uses together, consider using a more generalized
approach such as that provided by the Boost pp-lib. In other words, in this
particular scenario, the pairs of struct names and types form a dataset:

#include <boost/preprocessor/seq/elem.hpp>
#include <boost/preprocessor/seq/for_each.hpp>

#define impl(r, data, pair) \
struct BOOST_PP_SEQ_ELEM(0, pair) { \
BOOST_PP_SEQ_ELEM(1, pair) obj; \
}; \
/**/

BOOST_PP_SEQ_FOR_EACH(
impl, ~,
( (my_class1)(my_member_type1) )
( (my_class2)(my_member_type2) )
( (my_class3)(my_member_type3) )
( (my_class4)(my_member_type4) )
( (my_class5)(my_member_type5) )
)

#undef impl

Or, if debuggability of the result is important, use file iteration (provided by
the Boost pp-lib) to iterate over the contents of the dataset.

Last, but not least, if the generator is only used a few times, get rid of the
generator altogether and just write it out. Code generation comes at the price
of having to understand another layer of computation. Make sure that that price
is worth paying. The things that can make it worth paying are convenience (i.e.
not manually writing a pattern of code over and over), extensibility (i.e.
making whatever it is that you're doing self-scaling), and maintenance (i.e.
localizing changes in the code pattern). But, don't underestimate the price of
having to understand another layer of computation. Not that code generation
should not be feared, but it also shouldn't be thoughtlessly applied to any
scenario where it could be.

Regards,
Paul Mensonides
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,432
Messages
2,571,680
Members
48,796
Latest member
Greg L.

Latest Threads

Top