1

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.

9
  • You might want to consider an upgrade from CVS. Can you explain why #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? Commented Nov 29, 2020 at 16:12
  • I will add more details about my problem. Commented Nov 29, 2020 at 16:13
  • If the library has two different versions, why do you need to specify a version on a file-by-file basis, rather than as a project level -DmyLib=2? Unrelated, in my C++ projects I'll have namespace 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. Commented Nov 29, 2020 at 16:15
  • 2
    If I understand correctly, you need to communicate with your version control system, which is obviously system specific. Some systems, at least with plugins, are able to insert version strings in the file upon check-in. While that facility is typically used to write comments it probably an be used in a define. Alterantively you could have a hand-crafted checkin script which updates a file which contains only a version number and is either read by the check-in script or included to initialize a static const or such. Commented Nov 29, 2020 at 16:58
  • 1
    This is what scripting is for. I have a build script that injects git commit hashes into the source code when building. Pretty much like the prehistoric SCMs like CVS did. It's my custom automated build script. C++ does not solve every problem in the world. Sometimes one needs to employ other approaches, like scripts, XML-generated code, etc... Pretty easy to hack up a custom build script like that when using plain Makefiles and scripts. But if one is handcuffed by a GUI IDE, with pretty menus and buttons, that's all you can do: look at pretty menus and buttons. No scripting capability at all. Commented Nov 29, 2020 at 18:06

1 Answer 1

0

I didn't completely get your motivation but you can consider achieve it with g++ by compiling each file separately

g++ -DmyLib=myLib_1_0_0 -c file1.cpp -o file1.o
g++ -DmyLib=myLib_2_0_0 -c file2.cpp -o file2.o
g++ -DmyLib=myLib_3_0_0 -c file3.cpp -o file3.o

g++ -o my_lib file1.o file2.o file3.o

or if you use CMake

set_source_files_properties(file1.cpp PROPERTIES COMPILE_DEFINITIONS myLib=myLib_1_0_0)
set_source_files_properties(file2.cpp PROPERTIES COMPILE_DEFINITIONS myLib=myLib_2_0_0)
set_source_files_properties(file3.cpp PROPERTIES COMPILE_DEFINITIONS myLib=myLib_3_0_0)

add_executable(my_lib file1.cpp file2.cpp file3.cpp)
Sign up to request clarification or add additional context in comments.

1 Comment

Sorry but this does not work for me. This sets the macro to one value for the whole compilation unit. Imagine the includes for version 1 and version 2 to be deep down in some nested includes of the consumer. In this case one of them will get the wrong value for the macro. This is why the header itself needs to define it somehow.

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.