简体   繁体   中英

Recursively including a header file over a list of values

Suppose I have an header that is meant to be included several times generating code from a template parameterised over a macro DATA . I use it in this way:

#define DATA this
#include <header.hpp>
#undef DATA

#define DATA that
#include <header.hpp>
#undef DATA

#define DATA the_other
#include <header.hpp>
#undef DATA

Is there a way to automate this repeated inclusion given a list of the values of DATA ? Something like:

#define DATAS (this, that, the_other)
#include <header.hpp>
#undef DATAS

I tried with some __VA_OPT__ magic, and inside of header.hpp I can isolate the first element of the list and the tail of the list, but the problem is that I cannot redefine DATAS in terms of itself for the next inclusion.

Is this possible at all?

I have to admit I wouldn't even consider using any preprocessing tricks for that. This is a classical scripting problem.

Instead you could write a small script that creates that header for you and inserts that at the beginning of the file. You could then add that as a step in your build system to run it. This technique gives you a LOT of power going forward:

  • You can add the same header to many scripts rather easily
  • You can see all the custom headers in a clean json format
  • You could easily get the script to add multiple #define <key> <value> -s before the include
  • You could change formatting easily and quickly

Here is an example script that does that:

import json

def prepend_headers(fout, headers):
    for header in headers:
        include = header['include']
        define = header['define']

        for k, v in define.items():
            fout.write(f'#define {k} {v}\n')

        fout.write(f'#include {include}\n')

        for k, _ in define.items():
            fout.write(f'#undef {k}\n')

        fout.write('\n')

def main(configfile):
    with open(configfile) as fin:
        config = json.load(fin)

    input_file = config['input']
    with open(input_file) as fin:
        input_content = fin.read()

    output_file = config['output']
    with open(output_file, 'w') as fout:
        headers = config['headers']
        prepend_headers(fout, headers)
        fout.write(input_content)

if __name__ == '__main__':
    import sys
    configfile = sys.argv[1]
    sys.exit(main(configfile))

If you use the following configuration:

{
    "input": "class.cpp.template",
    "output": "class.cpp",
    "headers": [
        {
            "include": "<header.hpp>",
            "define": {
                "DATA": "this",
                "OBJ": "him"
            }
        },
        {
            "include": "<header.hpp>",
            "define": {
                "DATA": "that"
            }
        },
        {
            "include": "<header.hpp>",
            "define": {
                "DATA": "the_other"
            }
        }
    ]
}

And the following template file:

#include <iostream>

class Obj {
};

int main() {
    Obj o;
    std::cout << "Hi!" << std::endl;
    return 0;
}

The result you get is this:

#define DATA this
#define OBJ him
#include <header.hpp>
#undef DATA
#undef OBJ

#define DATA that
#include <header.hpp>
#undef DATA

#define DATA the_other
#include <header.hpp>
#undef DATA

#include <iostream>

class Obj {
};

int main() {
    Obj o;
    std::cout << "Hi!" << std::endl;
    return 0;
}

Using a template class might be annoying, so you might decide to add some hints in the output file so you could "replace" them with every build you run.

Yes, it is possible.

You can use Boost Preprocessor (which is independent of all other Boost Packages and only has to be downloaded, no library needs to be built or installed) to get the needed ready-to-use macros. You can also try to understand Boost Preprocessor and recreate the needed features.

The example is taken from Ari's answer. It could be expanded to provide several data elements to each iteration, eg for initializing the int s and float s with specific values.

// header.hpp - sample header, which uses DATA to create variables
// uses Boost preprocessor only for simple concatenation
// you can use your custom header here

#include <boost/preprocessor/cat.hpp>

int BOOST_PP_CAT(int_, DATA) = 1;
float BOOST_PP_CAT(float_, DATA) = 2.2f;
// main.cpp - wants to define lots of variables
// provides header name, list of symbol suffixes

// repeated.hpp will include header.hpp 3 times with DATA set to this, that and the_other
// (Space after REP_PARAMS is important)

#define REP_PARAMS ("header.hpp")(this, that, the_other)
#include "repeated.hpp"
#undef REP_PARAMS

#include <iostream>
using namespace std;

int main()
{
    cout << "int_this: " << int_this << endl;
    cout << "int_that: " << int_that << endl;
    cout << "int_the_other: " << int_the_other << endl;
    cout << "----------------------------------------------------------"
        << endl;
    cout << "float_this: " << float_this << endl;
    cout << "float_that: " << float_that << endl;
    cout << "float_the_other: " << float_the_other << endl;

    return 0;
}
// repeated.hpp - helper header
// all the magic
// it mostly extracts the REP_PARAMS sequence
// TODO error-checking, e.g. that REP_PARAMS exists and is a sequence of length two, that second element of REP_PARAMS is a tuple

#if !BOOST_PP_IS_ITERATING
// iteration has not started yet, include used boost headers
// initialize iteration with 3 parameters from 0 to < size of tuple,
// include itself (repeated.hpp)

#include <boost/preprocessor/iteration/iterate.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/tuple/size.hpp>
#include <boost/preprocessor/seq/seq.hpp>

#define BOOST_PP_ITERATION_PARAMS_1 (3, (0, BOOST_PP_TUPLE_SIZE(BOOST_PP_SEQ_TAIL(REP_PARAMS)), "repeated.hpp"))
#include BOOST_PP_ITERATE()

#else
// set DATA to i-th element in tuple, include specified header (header.hpp)

#define DATA BOOST_PP_TUPLE_ELEM(BOOST_PP_ITERATION(), BOOST_PP_SEQ_TAIL(REP_PARAMS))
#include BOOST_PP_SEQ_HEAD(REP_PARAMS)
#undef DATA

#endif

The maximum list size is 256. By default it is limited to 64, but can be increased with the BOOST_PP_LIMIT_TUPLE macro.

This is not doable using preprocessor only. However, it is probably worth mentioning that there is something called X-Macro that could have been used for something close to what you are asking if you weren't using preprocessor macros for each case.

The reason is that it cannot be used here is that you cannot use #define or #include in the definition of a macro.

For example, this is doable for defining this , that and the_other as variables from a file called data.def that has them as a list:

// data.def
ELEMENT(this)
ELEMENT(that)
ELEMENT(the_other)

Then in main.cc :

//main.cc
#define ELEMENT(d) int int_##d = 1;
#include "data.def"
#undef ELEMENT

#define ELEMENT(d) int float_##d = 2.2;
#include "data.def"
#undef ELEMENT

int main() {
  std::cout << "int_this: " << int_this << std::endl;
  std::cout << "int_that: " << int_that << std::endl;
  std::cout << "int_the_other: " << int_the_other << std::endl;
  std::cout << "----------------------------------------------------------"
            << std::endl;
  std::cout << "float_this: " << float_this << std::endl;
  std::cout << "float_that: " << float_that << std::endl;
  std::cout << "float_the_other: " << float_the_other << std::endl;
}

Output:

int_this: 1
int_that: 1
int_the_other: 1
---------------------------------------------------------------
float_this: 2
float_that: 2
float_the_other: 2

But something like this is not going to work because you would be defining a macro in another macro:

#define ELEMENT(d) #define DATA d; \
#include "data.def" \
#undef DATA
#undef ELEMENT

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM