Boost logo

Boost :

From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2006-08-30 06:18:40


> -----Original Message-----
> From: boost-bounces_at_[hidden]
> [mailto:boost-bounces_at_[hidden]] On Behalf Of
> cppljevans_at_[hidden]

> The test driver in http://boost-consulting.com/vault
> /Preprocessor Metaprogramming/ctor_template.zip only works if
> the ctor_template.hpp file #undef's the include guard as follows:
>
> #undef boost_forwarder_ctor_template_included
>
> However, this need is not mentioned in:
> http://www.boost.org/libs/preprocessor/doc/topics/file_iteration.html
>
> Maybe it should be mentioned there or am I missing something?

The file "ctor_template.hpp" is not being used like a normal header. Rather, it
is being used in a fashion similar to the body of code iterated by the
file-iteration mechanism. IOW, the #include directive that includes it is
acting like a function call (or macro, if you will). For this type of use,
"ctor_template.hpp" shouldn't have an #include guard at all (at least, not a
normal one), as it is *intended* to be included multiple times. Also, it is
using what the pp-lib calls "named external arguments", which, for all intents
and purposes are arguments to the "function call". The header should #undef
those macros:

// boost/forwarder/ctor_template.hpp
#if BOOST_PP_IS_ITERATING

    // ----- point 1 ----- //

    // "include guard" (to save time)
    #ifndef BOOST_FORWARD_CTOR_TEMPLATE_INCLUDED
    #define BOOST_FORWARD_CTOR_TEMPLATE_INCLUDED
        #include <boost/preprocessor/iterate.hpp>
        #include <boost/preprocessor/repetition/enum_binary_params.hpp>
        #include <boost/preprocessor/repetition/enum_params.hpp>
    #endif

    // ----- point 2 ----- //

    // sanity check for the "named external arguments"
    #if !defined(BOOST_FORWARDER_CTOR_TEMPLATE_THIS_TYPE) \
        || !defined(BOOST_FORWARDER_CTOR_TEMPLATE_BASE_TYPE) \
        /**/
        #error ...choke and die!
    #endif

    // initiate iteration...
    #define BOOST_PP_ITERATION_LIMITS \
        (0, ( \
            (BOOST_FORWARDER_CTOR_TEMPLATE_MAX_ARITY) \
            ? (BOOST_FORWARDER_CTOR_TEMPLATE_MAX_ARITY) : 4) - 1) \
        /**/
    #define BOOST_PP_FILENAME_1 <boost/forwarder/ctor_template.hpp>
    #include BOOST_PP_ITERATE()

    // automatically #undef "named external arguments"
    #undef BOOST_FORWARDER_CTOR_TEMPLATE_THIS_TYPE
    #undef BOOST_FORWARDER_CTOR_TEMPLATE_BASE_TYPE

#else
    #define n BOOST_PP_ITERATION() // This is a local macro.
                                   // Non-all-caps is okay
                                   // for local macros only!

    #if !n
        BOOST_FORWARDER_CTOR_TEMPLATE_THIS_TYPE(void)
    #else
        template<BOOST_PP_ENUM_PARAMS(n, typename U)>
        BOOST_FORWARDER_CTOR_TEMPLATE_THIS_TYPE
        ( BOOST_PP_ENUM_BINARY_PARAMS(n, U, _) )
    #endif
        BOOST_FORWARDER_CTOR_TEMPLATE_BASE_TYPE(n) { }

    #undef n
#endif

Uses of the above (hopefully I didn't make any mistakes) look like (e.g. line 27
of "ctor_template_test.cpp"):

#define BOOST_FORWARDER_CTOR_TEMPLATE_THIS_TYPE sub
#define BOOST_FORWARDER_CTOR_TEMPLATE_BASE_TYPE(n) :
Super(BOOST_PP_ENUM_PARAMS(n, _))
#include "boost/forwarder/ctor_template.hpp"
// note: no #undef's here!

That's all step one. Step two is to make this a proper interface, and to make
the #include in the code immediately above *look* like what it is--not a normal
header inclusion.

1) Rename "boost/forwarder/ctor_template.hpp" to
"boost/forwarder/detail/ctor_template.hpp".

2) Pull everything from "point 1" to "point 2" out of this file and put it in a
new "boost/forwarder/ctor_template.hpp".

3) Add the following line after the header inclusions:

#define BOOST_FORWARDER_CTOR_TEMPLATE() \
    <boost/forwarder/detail/ctor_template.hpp> \
    /**/

After this, the client code looks like:

#include <boost/forwarder/ctor_template.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>

// ...

#define BOOST_FORWARDER_CTOR_TEMPLATE_THIS_TYPE sub
#define BOOST_FORWARDER_CTOR_TEMPLATE_BASE_TYPE(n) :
Super(BOOST_PP_ENUM_PARAMS(n, _))
#include BOOST_FORWARDER_CTOR_TEMPLATE()

Overall, the concept is that a file-inclusion can be viewed as a function call
that does something--namely insert code into the translation unit. Adding
#include-guards makes a header *not* like a function call. Or, I should say, it
makes it like a function call that does nothing every time it is used after the
first time. File-iteration works by repeatedly calling such a function. In the
code above, the function that it calls is also the file that initiated the
file-iteration. The function itself disambiguates how it is being called with
BOOST_PP_IS_ITERATING. What the file-iteration mechanism is basically doing is
defining a for-loop mechanism that calls a user-defined function:

namespace boost { namespace preprocessor {
    bool is_iterating;

    template<class F> void iterate(int a, int z, F func) {
        is_iterating = true;
        for (; a <= z; ++a) {
            func(a);
        }
        is_iterating = false;
        return;
    }
}}

The "ctor_template.hpp" header (as it currently exists) is such a function, but
its initial call (the one that starts the iteration) is done by the client, not
the file-iteration mechanism. You have to write that file as if it is a
function--not a header. But you should also provide a normal header that
defines the interface to the function (i.e. BOOST_FORWARDER_CTOR_TEMPLATE()) and
includes the files needed by the function.

Make sense?

In the grand scheme of things, running a file through the preprocessor (with the
compiler already does) is the (more or less line by line) execution of a program
whose output is fed to the underlying language parser. Both macros and headers
are types of functions in this program (aside from directives, everything else
is implicitly the output of the program). Both macro invocations and #include
directives are types of function calls. Both the definition of a macro (i.e.
#define) and the creation of a header are definitions of those functions.
Viewing the process of preprocessing this way is an very enabling
point-of-view--even with normal headers. Include guards are just used so that a
function outputs what it outputs only once. File-iteration ties into this as
non-recursive looping construct. (It is necessarily non-recursive because most
preprocessors "helpfully" error on recursive includes of the same file at a very
shallow depth.) Granted, the rules of the language in which this program is
written are strange, but they are nevertheless quite usable.

Regards,
Paul Mensonides


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk