简体   繁体   中英

Using a macro to define an include file

To make a long story short, I'm working on some code that needs different header files based on various configuration options. I could do something like this:

#if OPTION == A
#include "relative/path/to/file/a.hpp"
#elseif OPTION == B
#include "relative/path/to/file/b.hpp"
#elseif OPTION = ...
...
#endif

This would work, but it seems like a really ugly solution for what could potentially be more than a handful files. I could also simply include all the various header files, but that, too, seems like a bit of an ugly solution, and it could pose problems in the future if files (for some horrible reason) start redefining the same objects. The idea I had was that something like the following would be nice, particularly in the context of how the rest of the code is written:

#define QUOTE(str) #str
#define STRINGIFY(A,B) QUOTE(A##B)
...
#include STRINGIFY(relative/path/to/option/,OPTION)

The problem with this seems to be two-fold:

  1. The OPTION definition doesn't expand properly inside STRINGIFY .
  2. If there are any forward slashes in the path name, STRINGIFY fails altogether, with g++ giving me an error along the lines of the following:

error: pasting "/" and "OPTION" does not give a valid preprocessing token

I can't seem to find any real information on why / is a bad character for the C++ preprocessor, just a few articles saying that you should just put "/" in and rely on automatic C++ string concatenation (which doesn't work in an #include statement). I'm willing to consider design alternatives if I'm just trying to do something really dumb, but I'd also like to figure out why this isn't working.

EDIT: I should make the clarification that I'm working on a codebase originally designed by a group of scientists. Reasonable coding conventions and typical expectations for how a code gets used go completely out the window. This code will probably be modified at least as many times as it gets used, often by people who have spent their entire careers writing Fortran 77 and think object-oriented programming is some new-fangled invention that just makes your code harder to understand.

When writing macros it is essential to look at the output after preprocessing. With gcc thats the -E option.

To include different files I would rely on your build tools rather than macro voodoo. For example when the code is

include option

Then gcc -E -option=\"foo.h\" yields

include "foo.h"

Note that I ommitted the # here, otherwise preprocessing would fail due to not finding "foo.h" .

I wanted to do something similar, searched around and found this post, and after trying few things I think I can help you.

So, your approach:

#define QUOTE(str) #str
#define STRINGIFY(A,B) QUOTE(A##B)
...
#include STRINGIFY(relative/path/to/option/,OPTION)

As I understand from https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html doesn't work because when using ## to paste tokens, the result must be a valid token, where something like path/to/file.hpp is not a valid preprocessing token.

Now, I think that you only need to have a macro which holds the path plus the token to be replaced. Well, you don't really need to add the relative path if you are using the -I flag. Your macro could look like #define myMacro relative/path/to/file/OPTION.hpp , then you just need to define OPTION and pass it to one macro so OPTION gets replaced with its value and then another macro to stringize. These two levels of macros are described herehttps://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing

The following example will ilustrate both cases (with/without -I flag).

Consider this directory structure:

.   
|-- a.c 
|-- headers
|   |-- A.hpp
|   `-- B.hpp
`-- makefile

Where ac:

#define header OPTION.hpp
#define xstr(x) #x
#define str(x) xstr(x)
#include str(header)

makefile:

PATH_OPTION ?= headers/A
OPTION ?= A

.PHONY: all 
all: a.c 
        cpp -D OPTION=${PATH_OPTION} $<
        cpp -I./headers -D OPTION=${OPTION} $<

A.hpp

#warning Including A.hpp
1   

B.hpp

#warning Including B.hpp
2   

With this, you can call make which by default will include headers/A.hpp and you should be able to see both approaches (with/without -I flag) working and printing to stdout the corresponding warning.

To include headers/B.hpp you would do make OPTION=B or make PATH_OPTION=headers/B depending on the approach you take.

I also tried with g++ instead of cpp and it works as well.

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