Context
I am currently trying to implement a C++ project setup that allows me to have two different versions of a library in one dependency tree. In order to do so I need to encode the version number in includes and the namespace. As far as I am aware this can only be done with the use of macros. So my first approach was to simply hardcode the definition of this macro into each source file like #define myLib myLib_1_0_0. This has the problem that I need to know the version of a file before I commit it. This is ugly, because I want the version to come from the VCS (Git in my case) and not to be duplicated in the each file. Also it requires changing each source file for each new version which would ruin the change log.
Problem
The problem is, that I am not able to come up with a mechanism/hack to get a per-file macro. With compiler -D options I can only set the macro for the whole compilation unit which means that it will be defined to the same value for all included headers. This is not what I want. How can I define a preprocessor macro for individual files?
Don't hesitate to suggest ugly or hacky solutions. This is only an experiment for now where I want to see if this is possible at all.
Ideas for a hacky solution
C++ offers the __FILE__ macro which is defined for individual files. I was hoping that I could maybe highcheck this macro to include another file that defines the macros I need. Here I am stuck because I fail to use the __FILE__ macro to create a differnt include path because it is already stringified.
Further Context and Motivation
Want I want is to find an experimental solution where I can link two different versions of a library to a consumer library and use them at the same time. My approach to make this work is to encode the version number into target names, include paths and namespaces, so it will look to the compiler and build-system as if the two versions are completely different dependencies.
This is what I have come up with so far:
A file VersionUtils.h that contains helper macros. It is force included in all compilation units of the library and the consumer.
// File VersionUtils.h
#define stringify(x) #x
#define VERSIONED_INCLUDE2(a, b) stringify( a ## / ## b)
#define VERSIONED_INCLUDE(a, b) VERSIONED_INCLUDE2(a, b)
#define VERSIONED_NAMESPACE(ns) namespace ns
The files of the library in version 1.0.0 with some dummy code.
// File MyLib.h
#define myLib myLib_1_0_0
VERSIONED_NAMESPACE(myLib)
{
int myFunction();
}
// File MyLib.cpp
#define myLib myLib_1_0_0
VERSIONED_INCLUDE(myLib, MyLib.h)
VERSIONED_NAMESPACE(myLib)
{
int myFunction()
{
return 42;
}
}
The files of the library in version 2.0.0 with some dummy code.
// File MyLib.h
#define myLib myLib_2_0_0
VERSIONED_NAMESPACE(myLib)
{
int myFunction();
}
// File MyLib.cpp
#define myLib myLib_2_0_0
VERSIONED_INCLUDE(myLib, MyLib.h)
VERSIONED_NAMESPACE(myLib)
{
int myFunction()
{
return 1337;
}
}
The consumer main.cpp file in the MyExe executable.
// File main.cpp
#include <iostream>
#define myLib1 myLib_1_0_0
#define myLib2 myLib_2_0_0
VERSIONED_INCLUDE(myLib1, MyLib.h)
VERSIONED_INCLUDE(myLib2, MyLib.h)
int main()
{
std::cout << myLib1::myFunction() << "\n"; // prints 42
std::cout << myLib2::myFunction() << "\n"; // prints 1337
}
To make this work, the header file of the library can not use the version number that is defined by a macro outside the header. It works when the definition is hardcoded into the header files, but this is not a toleratable solution due to the above mentioned problems.
Also note that this is only a simplification of the more complicated real-world case where the two versions of the same library are deep down in a large dependency tree where the consumer may not have control over the used versions of myLib.
#defineing a macro at the beginning of each header file (but after it includes any other header files) and then#undefing it at the end won't work for you?-DmyLib=2? Unrelated, in my C++ projects I'll havenamespace myLib { namespace v1 { ... } namespace v2 { ... }}to organize versions, which allows some incremental migration path. Especially if a function changes its return type or noexcept tag or parameters.