I have a large legacy c++ code base using headers and implementations in hpp/cpp files. This needs to be available still for older compiler compatibility.
Example files
// myLib.hpp
int foo();
// myLib.cpp
#include "myLib.hpp"
int foo() { return 42; }
// main.cpp
#include "myLib.hpp"
int main() { return foo(); }
For newer compilers I would like to be able to compile specific headers into modules (e.g. "myModule"). Like a wrapper around the hpp/cpp files.
This should not be a replacement as some depending translation units might still use #include "myLib.hpp"
whereas others might already use import myModule;
.
If a module should be build and used at all could be selected via a CMake option that gets translated into a preprocessor macro, e.g.
# CMakeLists.txt
option(CMAKE_USE_MODULES "Enable C++20 module usage" ON)
if(CMAKE_USE_MODULES)
add_compile_definitions(USE_MODULES)
endif(CMAKE_USE_MODULES)
My question is how the module interface and its implementation should be implemented?
// myLib.hpp
EXPORT int foo();
// myLib.cpp
#define EXPORT
#include "myLib.hpp"
int foo() { return 42; }
// myModule.cpp
export module myModule;
#define EXPORT export
#include "myLib.hpp"
// myModule_impl.cpp
module myModule;
#include "myLib.cpp"
Other translation units now have the option to use the header or the module, e.g.
// tu_alwaysHeader.cpp
#define EXPORT
#include "myLib.hpp"
int alwaysHeader() { return foo(); }
// tu_tryModuleOtherwiseHeader.cpp
#ifdef USE_MODULES
import myModule;
#else
#define EXPORT
#include "myLib.hpp"
#endif
int tryModuleOtherwiseHeader() { return foo(); }
Another question: How should the dependency be represented in CMake? Especially myModule.cpp onto myLib.hpp and myModule_impl.cpp onto myLib.cpp?
Minimal example on godbolt.
This should not be a replacement as some depending translation units might still use
#include "myLib.hpp"
whereas others might already useimport myModule;
.
Well that's a problem.
C++ modules are not just an alternative way of accessing an entity. To put an entity declaration/definition in the purview of a module changes the nature of that entity. Specifically, it now has module linkage.
Two declarations that name the same entity must agree on that entity's linkage. This must be true no matter where they are declared within your entire program, not just one file.
There are several rules around this, but they almost always boil down to the same thing: ill-formed, no diagnostic required. This means that your code may compile and link, but what happens next is unknowable. It may work... until it doesn't.
A library can have a module and non-module build for itself. But you have to treat these as different versions of the library. Different, incompatible versions. All the code in your program must agree on which version to use.