Construct Path for #Include Directive with MACro

Construct path for #include directive with macro

I tend to agree with the comment in utnapistim's answer that you shouldn't do this even though you can. But, in fact, you can, with standard-conformant C compilers. [Note 1]

There are two issues to overcome. The first one is that you cannot use the ## operator to create something which is not a valid preprocessor token, and pathnames do not qualify as valid preprocessor tokens because they include / and . characters. (The . would be ok if the token started with a digit, but the / will never work.)

You don't actually need to concatenate tokens in order to stringify them with the # operator, since that operator will stringify an entire macro argument, and the argument may consist of multiple tokens. However, stringify respects whitespace [Note 2], so STRINGIFY(Dir File) won't work; it will result in "directory/ filename.h" and the extraneous space in the filename will cause the #include to fail. So you need to concate Dir and File without any whitespace.

The following solves the second problem by using a function-like macro which just returns its argument:

#define IDENT(x) x
#define XSTR(x) #x
#define STR(x) XSTR(x)
#define PATH(x,y) STR(IDENT(x)IDENT(y))

#define Dir sys/
#define File socket.h

#include PATH(Dir,File)

Warning: (Thanks to @jed for passing on this issue.) If the strings being concatenated contain identifiers which are defined elsewhere as macros, then unexpected macro substitution will occur here. Caution should be taken to avoid this scenario, particularly if Dir and/or File are not controlled (for example, by being defined as a command-line parameter in the compiler invocation).

You need to also be aware than some implementations may define words which are likely to show up in a token-like fashion in a file path. For example, GCC may define macros with names like unix and linux unless it is invoked with an explicit C standard (which is not the default). That could be triggered by paths like platform/linux/my-header.h or even linux-specific/my-header.h.

To avoid these issues, I'd recommend that if you use this hack:

  • you use a C (or C11) standards-conformant compiler setting, and

  • you place the sequence very early in your source file, ideally before including any other header, or at least any header outside of the standard library.

Also, you wouldn't need the complication of the IDENT macro if you could write the concatenation without spaces. For example:

#define XSTR(x) #x
#define STR(x) XSTR(x)

#define Dir sys
#define File socket.h

#include STR(Dir/File)


Notes

  1. I tried it with clang, gcc and icc, as available on godbolt. I don't know if it works with Visual Studio.

  2. More accurately, it semi-respects whitespace: whitespace is converted to a single space character.

How to use a macro in an #include directive?

You can use a series of macros to create your include file. Unfortunately, I can't think of any cleaner (in-source) way of doing this. This works for arm-eabi-none-gcc v5.4.1.

#define LIBC_DIR ../../../../lib/libc++/

#define STRINGIFY_MACRO(x) STR(x)
#define STR(x) #x
#define EXPAND(x) x
#define CONCAT(n1, n2) STRINGIFY_MACRO(EXPAND(n1)EXPAND(n2))
#define CONCAT5(n1, n2, n3, n4, n5) STRINGIFY_MACRO(EXPAND(n1)EXPAND(n2)EXPAND(n3)EXPAND(n4)EXPAND(n5))

// Concatenate the five elements of your path.
// Of course, this can be simplified if there is only a prefix and a suffix
// that needs to be added and the ARCH_FAMILY, /, and ARCH are always present
// in the macro-generated #include directives.
#include CONCAT5(LIBC_DIR,ARCH_FAMILY,/,ARCH,/stkl/printkc/printkc.h)

Attempt to use preprocessor macros to generate include paths

It is not that "gcc thinks it is an error". It is an error. ("If the result [of the concatenation] is not a valid preprocessing token, the behavior is undefined.", C11 §6.10.3.3/3).

But that doesn't matter because you can stringify more than one token; there is no need to concatenate. You just have to watch out for whitespace.

See https://stackoverflow.com/a/32077478/1566221 for an example.

How do I use a preprocessor macro inside an include?

Is this valid C/C++?

The usage is valid C, provided that the macro definition is in scope at the point where the #include directive appears. Specifically, paragraph 6.10.2/4 of C11 says

A preprocessing directive of the form

# include pp-tokens new-line

(that does not match one of the two previous forms) is permitted. The
preprocessing tokens after include in the directive are processed just
as in normal text. (Each identifier currently defined as a macro name
is replaced by its replacement list of preprocessing tokens.
) The
directive resulting after all replacements shall match one of the two
previous forms.

(Emphasis added.) Inasmuch as the preprocessor has the same semantics in C++ as in C, to the best of my knowledge, the usage is also valid in C++.

What is the rationale behind these macros?

I presume it is intended to provide for indirection of the header name or location (by providing alternative definitions of the macro).

How can I convince Clang to parse these headers?

Provided, again, that the macro definition is in scope at the point where the #include directive appears, you shouldn't have to do anything. If indeed it is, then Clang is buggy in this regard. In that case, after filing a bug report (if this issue is not already known), you probably need to expand the troublesome macro references manually.

But before you do that, be sure that the macro definitions really are in scope. In particular, they may be guarded by conditional compilation directives -- in that case, the best course of action would probably be to provide whatever macro definition is needed (via the compiler command line) to satisfy the condition. If you are expected to do this manually, then surely the build documentation discusses it. Read the build instructions.

Concatenating preprocessor definition and string to create #include path

There are two things that are causing your problem.

  1. You are trying to turn "A" "B" into "AB".
  2. #include "A""B" is not valid syntax.

What you can do, is concatenate A and B and then turn that into a string literal.

#define STR_IMPL(A) #A
#define STR(A) STR_IMPL(A)

Then you can do this:

#define RESOURCE_FILE resource.h
#define PROJECT_DIRECTORY /foo/bar
#define RESOURCE_PATH STR(PROJECT_DIRECTORY/RESOURCE_FILE)

#include RESOURCE_PATH

Unfortunately, there's no way to turn "A" into A or "A""B" into "AB" in C++ preprocessor. So, you have to work with tokens without quotes and stringize the result at the end.

C preprocessor: building a path string

[cpp.include]/4:

A preprocessing directive of the form

        # include pp-tokens new-line

(that does not match one of the two previous forms) is permitted. The
preprocessing tokens after include in the directive are processed
just as in normal text (i.e., each identifier currently defined as a
macro name is replaced by its replacement list of preprocessing
tokens). If the directive resulting after all replacements does not
match one of the two previous forms, the behavior is
undefined.152


152 Note that adjacent string literals are not
concatenated into a single string literal (see the translation phases
in 2.2); thus, an expansion that results in two string literals is an
invalid directive.

So though #include MACRO is valid, MACRO must directly expand to an otherwise valid argument to #include. The concatenation of string literals happens two translation phases after preprocessing.

Also, in the definition of the ## operator, [cpp.concat]/3:

For both object-like and function-like macro invocations, before the replacement list is reexamined for more
macro names to replace, each instance of a ## preprocessing token in the replacement list (not from an
argument) is deleted and the preceding preprocessing token is concatenated with the following preprocessing
token
.
[..] If the result is not a valid preprocessing token, the behavior is undefined.

Hence the result of A##B must be one valid preprocessing token. / is an own preprocessing token, and so are the names of the directories and files.

You can't concatenate "abc and /xyz", since abc/ is not a valid preprocessing token - "abc is not one preprocessing token, but two, though "abc" is one.

On the other hand, if you concatenate <abc/ and xyz>, then / and xyz are concatenated, examined, and we have a problem again.

Thus it appears to be impossible to concat the paths using ##. Your approach looks quite impossible to me, too.


With GCC, this is fine though:

#define PATH <foo/bar/
#define FILE boo>

#define ARG PATH FILE
#include ARG

It works because GCCs preprocessor removes the white space (for some reason). Does not work on VC++ or Clang and isn't covered by standard anyway, so definitely not recommended.

Preprocessor concatenation for include path

You can't customise the search path for include files like this, but you can tell the compiler where to look for include files. Many compilers -I option for that, e.g.:

gcc -c stuff.c -I/path/to/my/ -I/path/to/other/

If that makes your compilation command too long, you should write a Makefile or, if you are working in Visual Studio or similar IDE, customise the search path in your project settings.

Include from preprocessor macro

It's easy once you stop thinking about token concatenation. Stringification works with any sequence of tokens, so there is no need to force its argument into being a single token. You do need an extra indirection so that the argument is expanded, but that's normal.

The only trick is to write the sequence without whitespace, which is what ID is for:

#define STRINGIFY(arg) STRINGIFY_(arg)
#define STRINGIFY_(arg) #arg
#define ID(x) x

#define VERSION 1.1.0
#include STRINGIFY(ID(other_)VERSION.h)

See https://stackoverflow.com/a/32077478/1566221 for a longer explanation.



Related Topics



Leave a reply



Submit