Home > Mobile >  Recursively including a header file over a list of values
Recursively including a header file over a list of values

Time:06-14

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?

CodePudding user response:

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 to #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.

CodePudding user response:

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 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
  • Related