# Header-only libraries and C++20 modules

**URL:** https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680
**Category:** Usage
**Created:** [April 17, 2024, 9:46am UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680 "2024-04-17T09:46:06Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![anarthal](https://discourse.cmake.org/user_avatar/discourse.cmake.org/anarthal/32/4486_2.png) [@anarthal](https://discourse.cmake.org/u/anarthal)
#### Post date: [April 17, 2024, 9:46am UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/1 "2024-04-17T09:46:06Z")

</div>

When installing a header-only library with a C++20 module interface, with something like:

```auto
add_library(mylib)
target_sources(mylib
    PUBLIC
        FILE_SET CXX_MODULES FILES mylib.cppm
        FILE_SET HEADERS FILES mylib.h
)
install(TARGETS mylib
    EXPORT mylib-targets
    FILE_SET CXX_MODULES
        DESTINATION lib/cmake/mylib/module
    FILE_SET HEADERS DESTINATION .
)

```

CMake will build and install a static or shared library, which seems just unnecessary, as the modules will be built again by the consumer. I’ve tried building an `INTERFACE` library, instead, but `target_sources` refuses to add a `CXX_MODULES` fileset with `INTERFACE` scope.

Is there a way to do this cleanly?

---

<div class="post-metadata">

### Author: ![robert.maynard](https://discourse.cmake.org/user_avatar/discourse.cmake.org/robert.maynard/32/4_2.png) [@robert.maynard](https://discourse.cmake.org/u/robert.maynard)
#### Post date: [April 17, 2024, 12:52pm UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/2 "2024-04-17T12:52:15Z")

</div>

@ben.boeckel

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [April 17, 2024, 5:47pm UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/3 "2024-04-17T17:47:52Z")

</div>

Modules have module initializer symbols that need to be provided once to be reliable (the ODR applies here). Providing it in a library is the reliable way to do this.

> [@anarthal](#):
>
> as the modules will be built again by the consumer

The BMIs will be built again, but not object files.

---

<div class="post-metadata">

### Author: ![anarthal](https://discourse.cmake.org/user_avatar/discourse.cmake.org/anarthal/32/4486_2.png) [@anarthal](https://discourse.cmake.org/u/anarthal)
#### Post date: [April 17, 2024, 9:15pm UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/4 "2024-04-17T21:15:43Z")

</div>

Sorry if it’s a dumb question, but what is a “module initializer symbol”? I can’t find any reference to the term in clang/MSVC docs or in the standard.

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [April 17, 2024, 11:57pm UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/5 "2024-04-17T23:57:58Z")

</div>

The new rule is that all global initializers required by module A will be called before those of module B if `B` contains `import A`. This is implemented by making a symbol for `export module M;` that is called for any `import M;` statement.

---

<div class="post-metadata">

### Author: ![autoantwort](https://discourse.cmake.org/user_avatar/discourse.cmake.org/autoantwort/32/5917_2.png) [@autoantwort](https://discourse.cmake.org/u/autoantwort)
#### Post date: [November 20, 2025, 4:07pm UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/6 "2025-11-20T16:07:39Z")

</div>

> The BMIs will be built again, but not object files.

Hm but we do not distribute object files.  
For example if we have a header only library like [argparse](https://github.com/p-ranav/argparse/) which has [a cppm file](https://github.com/p-ranav/argparse/blob/master/module/argparse.cppm), I currently have to create a static library and build to module (?) but the only thing I want is to distribute the cppm file and a target that consuming projects can link to.  
Currently I have

```cmake
add_library(argparse-modules INTERFACE)
add_library(argparse::argparse-modules ALIAS argparse-modules)
target_link_libraries(argparse-modules INTERFACE argparse)
target_compile_features(argparse-modules INTERFACE cxx_std_20)
target_sources(argparse-modules INTERFACE FILE_SET CXX_MODULES FILES module/argparse.cppm )

  install(TARGETS argparse argparse-modules EXPORT argparseConfig FILE_SET CXX_MODULES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/modules")
# ... Rest of https://github.com/p-ranav/argparse/blob/d924b84eba1f0f0adf38b20b7b4829f6f65b6570/CMakeLists.txt#L41

```

Or I am missing something?

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [November 20, 2025, 5:54pm UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/7 "2025-11-20T17:54:25Z")

</div>

Something needs to provide the `import_argparse_initializer()` symbol (mangled as the ABI requires) to call so that global statics are properly initialized. I floated the idea in ISO (without a paper) to have a `[[no_module_init]]` attribute on the `export module` to indicate that the module does not need it (note that this also makes declarations about imported modules’ requirements) and therefore a module would not need to worry about the ODR and its module initialization function.

---

<div class="post-metadata">

### Author: ![anarthal](https://discourse.cmake.org/user_avatar/discourse.cmake.org/anarthal/32/4486_2.png) [@anarthal](https://discourse.cmake.org/u/anarthal)
#### Post date: [November 20, 2025, 6:07pm UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/8 "2025-11-20T18:07:18Z")

</div>

As an idea, could CMake build the required object file in the consumer project? In a similar fashion as when building the BMI, and only when the module FILE\_SET is marked in the provided project as an INTERFACE property?

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [November 21, 2025, 4:17am UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/9 "2025-11-21T04:17:52Z")

</div>

If someone can explain why that doesn’t cause problems with the ODR (either because it doesn’t provoke it or somehow shows how it is OK to ignore in this situation) when used in multiple targets that end up linked together later…ṡure. Currently CMake rejects `INTERFACE FILE_SET` of type `CXX_MODULES` because of it.

---

<div class="post-metadata">

### Author: ![ChuanqiXu9](https://discourse.cmake.org/user_avatar/discourse.cmake.org/chuanqixu9/32/3263_2.png) [@ChuanqiXu9](https://discourse.cmake.org/u/ChuanqiXu9)
#### Post date: [November 24, 2025, 8:47am UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/10 "2025-11-24T08:47:48Z")

</div>

Users can hide symbols with linker version map.

Linking duplicated module is clearly not welcomed but I think cmake should leave it to linker to diagnose. The check in cmake side seems to be an overdesign to me.

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [November 24, 2025, 5:27pm UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/11 "2025-11-24T17:27:03Z")

</div>

> [@ChuanqiXu9](#):
>
> Users can hide symbols with linker version map.

Uhh…that sounds super platform-specific to me. “No one” will do that.

> [@ChuanqiXu9](#):
>
> Linking duplicated module is clearly not welcomed but I think cmake should leave it to linker to diagnose. The check in cmake side seems to be an overdesign to me.

Once we start making BMIs per importer configuration rather than assuming everything is compatible, I think we can do a survey of the actual risk with lifting the gate here. I just can’t bring the horses back into the barn if I allow it today and it is a problem. Plus, plumbing through `INTERFACE` modules is more complicated and would have delayed things even more (we might not find out we have modules until generate time when generator expressions are resolved and then we’re at a severe loss of where to hang any diagnostics[1] or (re-)scheduling things that need to happen for module-aware targets).

[1] At best, it’d all get reported on `target_link_libraries` which adds the module-providing target to the list.

---

<div class="post-metadata">

### Author: ![ChuanqiXu9](https://discourse.cmake.org/user_avatar/discourse.cmake.org/chuanqixu9/32/3263_2.png) [@ChuanqiXu9](https://discourse.cmake.org/u/ChuanqiXu9)
#### Post date: [November 25, 2025, 2:49am UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/12 "2025-11-25T02:49:13Z")

</div>

> [@ben.boeckel](#):
>
> Uhh…that sounds super platform-specific to me. “No one” will do that.

No, we did that in a lot of places (Although we’re using bazel right now). I do think it is not true to say “no one“.

> Once we start making BMIs per importer configuration rather than assuming everything is compatible, I think we can do a survey of the actual risk with lifting the gate here.

This makes me confusing. I don’t feel it relates to BMI. It is basic traditional linking problems.

I understand a lot of people today are concerning about “safe“ things. (Although it looks like we don’t have a consistent idea for “safe“). And I feel a lot of tools people are trying to force people to write “safe“ codes. But they seems to be an overkill to me. (including cmake doesn’t allow implementation partition units to not produce BMI).

And for the specific problem doesn’t relate to “safe“ issues. They will get the duplicated symbol by linker naturally if they did thing wrong. No body will get hurted.

---

<div class="post-metadata">

### Author: ![anarthal](https://discourse.cmake.org/user_avatar/discourse.cmake.org/anarthal/32/4486_2.png) [@anarthal](https://discourse.cmake.org/u/anarthal)
#### Post date: [December 3, 2025, 9:49am UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/13 "2025-12-03T09:49:24Z")

</div>

Let me know if I’m understanding this correctly:

- The provider project installs a header-only library with an `INTERFACE FILE_SET` of type `CXX_MODULES`.
- The consumer project finds it using `find_package`.
- The consumer builds an executable linking to the provider lib, using a set of compiler flags (e.g. C++ standard level, enabled extensions) of F1. This builds the provider lib with F1. Everything OK.
- The consumer builds a library linking to the provider lib, with flags F2. This would require building the provider lib with F2.
- The consumer finally builds an executable linking to both the provider lib and the lib built in the previous point, with flags F3. You’d try to build the provider lib again with F3, link it together, and that would create problems.

Is this the problem that would happen here? Wouldn’t something similar happen with BMIs, too?

---

<div class="post-metadata">

### Author: ![vito.gamberini](https://discourse.cmake.org/user_avatar/discourse.cmake.org/vito.gamberini/32/4376_2.png) [@vito.gamberini](https://discourse.cmake.org/u/vito.gamberini)
#### Post date: [December 7, 2025, 3:06am UTC](https://discourse.cmake.org/t/header-only-libraries-and-c-20-modules/10680/14 "2025-12-07T03:06:07Z")

</div>

BMIs are orthogonal here, you can think of them like PCH files, nothing in them violates ODR for the same reasons headers with only inline symbols don’t violate ODR.

The `_ZGIW<module>` symbol will violate ODR if it has multiple providers.
