I cannot understand what I am doing wrong in this simple test project using C++20 modules (available here):
# CMakeList.txt
cmake_minimum_required (VERSION 3.29)
project (ModulesGoogleTest LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
add_executable(mgtest)
target_sources(mgtest
PUBLIC
main.cpp
)
target_sources(mgtest
PUBLIC
FILE_SET mgtest_modules TYPE CXX_MODULES FILES sample.cppm sample.cpp)
target_compile_features(mgtest PUBLIC cxx_std_23)
.
/// main.cpp
import testmodule;
int main()
{
Counter c;
c.Print();
c.Increment();
c.Print();
return 0;
}
.
/// sample.cppm
module; // necessary?
module testmodule;
export module testmodule;
export class Counter
{
private:
int count;
public:
Counter() : count(0) {}
int Increment();
int Decrement();
void Print() const;
};
.
/// sample.cpp
#include <iostream>
module testmodule;
//import <iostream>; // same result
int Counter::Increment() { return ++count; }
int Counter::Decrement() {
if (count == 0) return count;
return --count;
}
void Counter::Print() const
{
std::cout << count << std::endl;
}
This is the error I get:
FAILED: ...
CMake Error: Output CMakeFiles/mgtest.dir/sample.cppm.o is of type `CXX_MODULES` but does not provide a module interface unit or partition
CMake Error: Output CMakeFiles/mgtest.dir/sample.cpp.o is of type `CXX_MODULES` but does not provide a module interface unit or partition
ninja: build stopped: subcommand failed.
I get exactly the same error building with Visual C++ 2022 and with Clang trunk.
I cannot find what is missing...
I'm adding another answer because I discovered more about modules. This won't be a direct answer to the OP. It will help others who find this question looking for information on modules.
The environment is CLion, GCC 14.2, and CMake 28.3. This is the earliest version of CMake that supports modules.
This a more complex scenario. The directory structure is:
../modules
├── CMakeLists.txt
└── hello_modules
├── CMakeLists.txt
├── hello
│ ├── CMakeLists.txt
│ ├── hello.cpp
│ └── hello.cppm
└── main.cpp
I explored how to do a separate compilation of the module's body and work with separate directories. In short, simulating a large project.
The top-level CMakeLists
is nothing unusual:
# top level CMakeLists.txt
cmake_minimum_required(VERSION 3.28.3)
project(modules)
add_compile_options(-std=c++23
-Werror -Wpedantic -Wall -Wno-unused-variable
-fno-exceptions
)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
add_subdirectory(hello_modules)
The property CMAKE_CXX_STANDARD_REQUIRED
needs to be on to trigger CMake
to request scanning of the C++ files for module information. This is a significant reason for the delay in getting Modules to work with CMake, requiring coordination between the compiler suppliers and CMake.
The next level down, CMakeLists,
creates the executable, including linking the modules. The module Hello
is in the subdirectory hello
and has been added to the file.
# hello_modules
cmake_minimum_required(VERSION 3.28.3)
project(ModuleTest)
add_subdirectory(hello) # build the module
add_executable(ModuleTest main.cpp)
target_link_libraries(ModuleTest Hello)
It wasn't clear whether CMake
would treat modules as a library via the target_link_libraries
command, but it does. The add_subdirectory
appears before the add_executable
to ensure the module is built before the executable.
Next, the module
# hello
cmake_minimum_required(VERSION 3.28.3)
project(Hello)
add_library(Hello)
target_sources(Hello
PUBLIC
FILE_SET CXX_MODULES
FILES hello.cppm
PRIVATE
hello.cpp # build body with module interface
)
set_target_properties(Hello PROPERTIES CXX_MODULE_TYPE GLOBAL)
One of the key learnings was to include the body with the target_sources
so that all the module files are built together. Again, CMake
treats a module like a library with the add_library
command.
The main.cpp
is straightforward. The only thing unique is the import Hello,
which accesses the module.
#include <iostream>
import Hello;
auto main() -> int {
constexpr mod::Hello hello;
std::cout << hello();
return 0;
}
Now the module interface, hello.cppm
:
export module Hello;
export namespace mod {
struct Hello {
auto operator()() const -> int;
};
}
The two exports create the module, hello,
and put struct Hello
in public view. The namespace mod
illustrates that everything within it can be exported with a single export.
Similarly, export {...}
exports all within the braces.
And, finally, the body of the module, 'hello.cpp:`
module;
#include <iostream>
module Hello;
namespace mod {
auto Hello::operator()() const -> int {
std::cout << "Hello modules" << '\n';
return 42;
}
}
The file starts with Module;
to indicate this is part of a module. The module Hello
tells which module. Again, the class method is inside the namespace, as usual. This file has no export
because the interface source made it available.