Undefined Reference error using modules in C++23 with clang and cmake

Hello,

I’m getting started with C++23 modules and trying to use them in a personal project.

The plan is to create an executable for the entire project, which would contain the output of all unit tests.

Each source cpp file has an implementation and a unit test. The module cppm file exports unit tests from all source files in the same directory.

These exported unit tests are then called in main.cpp.

The project layout is:

.
|---- CMakeLists.txt
|---- src
|      |---- foo
|             |---- add.cpp
|             |---- subtract.cpp
|             |---- foo.cppm
|
|---- main.cpp

As I make more progress on this project, there will be other directories in src/ similar to foo/.

Below are the file contents (collapsing to improve overview)

`src/foo/add.cpp` (`subtract.cpp` is similar)
import std;
#include <cassert>

class AddNumbers {
public:
    int add(int a, int b) {
        return a + b;
    }
};

void testAddNumbers() {
    AddNumbers solution;

    int result = solution.add(2, 2);
    int expected = 4;

    assert(result == expected); // Assert the result
}
`src/foo/foo.cppm`
export module foo;

export void testAddNumbers();
export void testSubtractNumbers();
`src/main.cpp`
import foo;

int main() {
    testAddNumbers();
    testSubtractNumbers();

    return 0;
}
`CMakeLists.txt`
cmake_minimum_required(VERSION 3.31)

set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "0e5b6991-d74f-4b3d-a41c-cf096e0b2508")
set(CMAKE_CXX_MODULE_STD ON)
set(CXXFLAGS "-Wall -Werror -O2 -Wno-error=sign-compare -Wno-pre-c++20-compat -std=c++23 -stdlib=libc++")
set(CMAKE_CXX_FLAGS "${CXXFLAGS}")
set(CMAKE_CXX_COMPILER clang++)

project(testmod VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS ON)

add_executable(testmod)
target_sources(testmod
    PUBLIC
    src/main.cpp
)
target_sources(testmod
  PUBLIC
    FILE_SET testmods TYPE CXX_MODULES
    BASE_DIRS
      ${PROJECT_SOURCE_DIR}
    FILES
      src/foo/foo.cppm
)

I’m compiling and building this project like:

cmake -G Ninja -S . -B build
ninja -C build

and on the second command I’m getting an error:

main.cpp:(.text+0x2): undefined reference to `testAddNumbers@foo()'
main.cpp:(.text+0x7): undefined reference to `testSubtractNumbers@foo()'

How can get the project to work correctly ?

Add a module foo; to the top of add.cpp and subtract.cpp without this, the compiler has no idea that the defined testAddNumbers symbol is part of the foo module and instead just makes a global module symbol instead.

I removed the build/ directory and ran

cmake -G Ninja -S . -B build
ninja -C build

the second command fails with:

ninja: Entering directory `build'
[12/12] Linking CXX executable testmod
FAILED: testmod 
: && /usr/bin/clang++ -Wall -Werror -O2 -Wno-error=sign-compare -Wno-pre-c++20-compat -std=c++23 -stdlib=libc++ -Xlinker --dependency-file=CMakeFiles/testmod.dir/link.d CMakeFiles/testmod.dir/src/main.cpp.o CMakeFiles/testmod.dir/src/foo/foo.cppm.o -o testmod  lib__cmake_cxx23.a && :
/usr/bin/ld: CMakeFiles/testmod.dir/src/main.cpp.o: in function `main':
main.cpp:(.text+0x2): undefined reference to `testAddNumbers@foo()'
/usr/bin/ld: main.cpp:(.text+0x7): undefined reference to `testSubtractNumbers@foo()'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.

Did you change the source at all?

Nope, apart from your suggestion there are no other changes.

Here’s the complete trace:

Terminal
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
% pwd
/home/bingletiger/Desktop/test
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
% tree .
.
├── CMakeLists.txt
└── src
├── foo
│   ├── add.cpp
│   ├── foo.cppm
│   └── subtract.cpp
└── main.cpp

3 directories, 5 files
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
% cat CMakeLists.txt
cmake_minimum_required(VERSION 3.31)

set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "0e5b6991-d74f-4b3d-a41c-cf096e0b2508")
set(CMAKE_CXX_MODULE_STD ON)
set(CXXFLAGS "-Wall -Werror -O2 -Wno-error=sign-compare -Wno-pre-c++20-compat -std=c++23 -stdlib=libc++")
set(CMAKE_CXX_FLAGS "${CXXFLAGS}")
set(CMAKE_CXX_COMPILER clang++)

project(testmod VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS ON)

add_executable(testmod)
target_sources(testmod
PUBLIC
src/main.cpp
)
target_sources(testmod
PUBLIC
FILE_SET testmods TYPE CXX_MODULES
BASE_DIRS
${PROJECT_SOURCE_DIR}
FILES
src/foo/foo.cppm
)
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
% cat src/main.cpp
import foo;

int main()
{
testAddNumbers();
testSubtractNumbers();

return 0;
}
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
% cat src/foo/add.cpp
module foo;

import std;
#include <cassert>

class AddNumbers {
public:
int add(int a, int b)
{
return a + b;
}
};

void testAddNumbers()
{
AddNumbers solution;

int result = solution.add(2, 2);
int expected = 4;

assert(result == expected); // Assert the result
}
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
% cat src/foo/subtract.cpp
module foo;

import std;
#include <cassert>

class SubtractNumbers {
public:
int subtract(int a, int b)
{
return a + b;
}
};

void testSubtractNumbers()
{
SubtractNumbers solution;

int result = solution.subtract(2, 2);
int expected = 0;

assert(result == expected); // Assert the result
}
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
% cat src/foo/foo.cppm
export module foo;

export void testAddNumbers();
export void testSubtractNumbers();
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
% cmake -G Ninja -S . -B build
-- The CXX compiler identification is Clang 20.1.3
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/clang++ - skipped
-- Detecting CXX compile features
CMake Warning (dev) at /usr/share/cmake/Modules/Compiler/CMakeCommonCompilerMacros.cmake:248 (cmake_language):
CMake's support for `import std;` in C++23 and newer is experimental.  It
is meant only for experimentation and feedback to CMake developers.
Call Stack (most recent call first):
/usr/share/cmake/Modules/CMakeDetermineCompilerSupport.cmake:113 (cmake_create_cxx_import_std)
/usr/share/cmake/Modules/CMakeTestCXXCompiler.cmake:83 (CMAKE_DETERMINE_COMPILER_SUPPORT)
CMakeLists.txt:9 (project)
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Detecting CXX compile features - done
-- Configuring done (0.3s)
-- Generating done (0.0s)
-- Build files have been written to: /home/bingletiger/Desktop/test/build
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
% ninja -C build
ninja: Entering directory `build'
[12/12] Linking CXX executable testmod
FAILED: testmod
: && /usr/bin/clang++ -Wall -Werror -O2 -Wno-error=sign-compare -Wno-pre-c++20-compat -std=c++23 -stdlib=libc++ -Xlinker --dependency-file=CMakeFiles/testmod.dir/link.dCMakeFiles/testmod.dir/src/main.cpp.o CMakeFiles/testmod.dir/src/foo/foo.cppm.o -o testmod  lib__cmake_cxx23.a && :
/usr/bin/ld: CMakeFiles/testmod.dir/src/main.cpp.o: in function `main':
main.cpp:(.text+0x2): undefined reference to `testAddNumbers@foo()'
/usr/bin/ld: main.cpp:(.text+0x7): undefined reference to `testSubtractNumbers@foo()'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
bingletiger@kdefw ~/Desktop/test
%
bingletiger@kdefw ~/Desktop/test
%