0

I am trying include a file constructed from pre-processor macros, but running into a wall due to rules regarding tokens, it seems. I used the answer here as a reference: Concatenate string in C #include filename, but my case differs in that there are decimal points in the define I am using to construct my include. This is what I have currently that will not get through the preprocessor stage: main.c:

#include <stdio.h>
#include <stdlib.h>

#define VERSION 1.1.0
#define STRINGIFY(arg) #arg
#define INCLUDE_HELPER(arg) STRINGIFY(other_ ##arg.h)
#define INCLUDE_THIS(arg) INCLUDE_HELPER(arg)

#include INCLUDE_THIS(VERSION)

int main(int argc, char **argv) {

   printf(INCLUDE_THIS(VERSION));
   fflush(stdout);
#if defined (SUCCESS)
   printf("\nSUCCESS!\n");
#endif
   return EXIT_SUCCESS;
}

other_1.1.0.h:

#define SUCCESS

Were I to use #define VERSION 1_1_0 and renamed the header accordingly it would work (but not viable for my use as I have no control over the name of the header files the actual project uses), but 1.1.0 is not a valid preprocessor token.

EDIT: After a bit more digging through the documentation, I see that 1.1.0 is a valid preprocessing number; it is the resulting concatenation of other_1.1.0 that is invalid. Regardless, the issue of not being able to construct the include remains.

11
  • Have you considered alternative ways of providing version-specific information so that you don't run into this conundrum? Or is that not possible? Commented Mar 26, 2018 at 19:43
  • @JonathanLeffler It is a hard requirement that a different header be included based on the version. There should be some flexibility in how to include the header. I am open to other options if it results in an elegant solution. Commented Mar 26, 2018 at 19:55
  • Ok; go back to those who set the requirement and ask them how to do it. If they don't know, maybe they shouldn't be setting the requirements? The developers are producing the version-specific headers (and won't change their scheme); ask them how they include the correct version in their test code, etc. Have you considered using symbolic links, for example, so you #include "other_version.h" but other_version.h is a symbolic link to other_1.1.0.h this week, and other_1.1.1.h next week, etc. Commented Mar 26, 2018 at 19:56
  • 1
    If you can do -DVERSION=${version} in a shell script/makefile, you could consider doing -DVERSION_HEADER='"other_${version}.h"' where the single quotes prevent the shell from stripping off the double quotes — though you can also arrange to stringify VERSION_HEADER inside your code. It's a bit of a nuisance, but fixes the problems with the preprocessor by not using the preprocessor to fix them. In the code, you write #include VERSION_HEADER under this scheme (or maybe #include STRINGIFY(VERSION_HEADER). Commented Mar 26, 2018 at 20:21
  • 1
    You might need to do the 'double macro dance' to get things stringified correctly. See SO 195975 for the # (stringification) variant, and SO 1489932 for the ## (token pasting) variant. Commented Mar 26, 2018 at 20:27

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Ah, that makes sense. The ID macro allows you to to place the VERSION token right up next to the prefix without use of the token-pasting operator bypassing the rules of token pasting.
2

With some experimentation, I came up with a solution that, while not ideal, could be workable.

#define VERSION _1.1.0
#define STRINGIFY(arg) #arg
#define INCLUDE_HELPER(arg) STRINGIFY(other ##arg.h)
#define INCLUDE_THIS(arg) INCLUDE_HELPER(arg)

#include INCLUDE_THIS(VERSION)

Rather than pasting other_ and 1.1.0 together, I am pasting other and _1.1.0. I am not sure why this is acceptable as the resulting token is the same, but there it is.

I would still prefer to have a solution that allows me to just define the version number without the underscore, so I will hold off on accepting this answer in case someone can come up with a more elegant solution (and works for people who don't happen to need an underscore anyways)

1 Comment

Token pasting combines two consecutive tokens. other_ is an identifier token 1.1.1 is a preprocessing number. The concatenation of those two strings is not a token. But the other way is quite different: other is an identifier token. So is _1. .1.1 is a ppnumber token, but it is not an argument of the token pasting operator. The operands are other and _1, which concatenate into the identifier token other_1. Stringification is then applied to the token sequence other_1 .1.1. Moral: stringification has nothing to do with tokenising.
1

If you are passing -DVERSION=1.1.0 as a compile-line parameter, rather than hard-wiring it in the source code, then there's nothing to stop you passing a second define using make or the shell to do the concatenation. For example, in a makefile, you might have:

VERSION = 1.1.0
VERSION_HEADER = other_${VERSION}.h

CFLAGS += -DVERSION=${VERSION} -DVERSION_HEADER=${VERSION_HEADER}

and then:

#include <stdio.h>
#include <stdlib.h>

#define STRINGIFY(arg) #arg
#define INCLUDE_HELPER(arg) STRINGIFY(arg)
#define INCLUDE_THIS(arg) INCLUDE_HELPER(arg)

#include INCLUDE_THIS(VERSION_HEADER)

int main(void)
{
   printf("%s\n", INCLUDE_THIS(VERSION));
#if defined (SUCCESS)
   printf("SUCCESS!\n");
#endif
   return EXIT_SUCCESS;
}

which is basically your code with the #define VERSION line removed, and using the stringified version of VERSION_HEADER instead of trying to construct the header name in the source code. You might want to use:

#ifndef VERSION
#define VERSION 1.1.0
#endif
#ifndef VERSION_HEADER
#define VERSION_HEADER other_1.1.0.h
#endif

for some suitable default fallback version in case the person running the compilation doesn't specify the information on the command line. Or you might use #error You did not set -DVERSION=x.y.z on the command line instead of setting the default value.

When compiled (source file hdr59.c):

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -DVERSION=1.1.0 \
>     -DVERSION_HEADER=other_1.1.0.h hdr59.c -o hdr59
$ ./hdr59
1.1.0
SUCCESS!
$

I would put the three lines of macro and the #include line into a separate small header so that it can be included when the version header is needed. If the default setting is required too, then that adds to the importance of putting the code into a separate header for reuse. The program's source code might contain:

#include "other_version.h"

and that header would arrange to include the correct file, more or less as shown.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.