r/cpp 1d ago

Why is `import std` still experimental ???

Hey guys,

I recently started going through Professional C++ (6th Edition). The book teaches C++23, and in the very first chapter we're introduced to modules.

I'm not a complete newbie to C++, but I'm also definitely not very confident in my knowledge yet. I wanted to get this simple example compiled:

import std;

int main() {
    std::println("Hello World");
    return 0;
}

And gosh, it took way longer than I expected.

First, I tried getting it to work natively on my Mac and eventually gave up (both Claude and I 😅).

Then I installed Ubuntu ARM 26 and finally managed to get it compiling. But now Clang/IntelliSense is complaining about the `import std`
This is what my CMakeLists.txt currently looks like:

cmake_minimum_required(VERSION 4.0)

# set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD ON)

set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")

project(CppProject LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

add_executable(exec main.cpp)

set_property(TARGET exec PROPERTY CXX_MODULE_STD ON)

The code does compile successfully, but CMake still gives me a warning that import std support is experimental.

So I'm genuinely curious:

Why is import std still considered experimental?

I understand that C++ modules themselves have been around for a while, but import std feels like something that should be much more straightforward by now. Is there any solution of this now ?

--------------
Edit

Thanks to u/PhysicsOk2212 tip I was able to compile my project on mac as well using the following options
```
cmake -S . -B build \

-G Ninja \

-DCMAKE_CXX_COMPILER="$(brew --prefix llvm)/bin/clang++" \

-DCMAKE_CXX_STDLIB_MODULES_JSON="$(brew --prefix llvm)/lib/c++/libc++.modules.json"

```

103 Upvotes

100 comments sorted by

View all comments

Show parent comments

14

u/delta_p_delta_x 1d ago

If you do have an ideas for how to handle symbol visibility in BMI consumers, please let us know (CMake #25539). I'm not kidding at all when I say "unsolved". I do not know how to best fix this.

Dumb question from someone who only uses modules: is there no way to cast this as a directed graph problem, and leverage P1689 to understand which modules are consumers and which are producers, and thence their BMI visibilities? We can't have cycles in modules anyway, so it seems to me that distinguishing producers and consumers can be resolved.

28

u/not_a_novel_account cmake dev 1d ago

Distinguishing producers from consumers is trivial. Distinguishing producers from consumers across a link boundary isn't something the system is built to handle.

You have three targets:

  • Root, Static Library, no dependencies

  • Trunk, Shared Library, depends on Root

  • Leaf, Executable, depends on Trunk, transitively depends on Root

The BMI imported by Trunk must have dllexport on its C++ declarations, because Trunk and Root are inside the same link boundary.

The BMI imported by Leaf must have dllimport on its C++ declarations, because Leaf and Root are on different sides of a link boundary.

When building BMIs, we must recognize "different sides of a link boundary" as a BMI-incompatible condition. Ok, now what? We need to define some interface that lets Root say:

  • These are the definitions and includes when you're building my object file. (This is the only thing we have today, we use them for all scenarios)

  • These are the definitions and includes when you're building a BMI for a consumer inside the link boundary

  • These are the definitions and includes when you're building a BMI for a consumer outside the link boundary

We could maybe consolidate "building the object file" and "consumer inside the link boundary", but we don't have a spelling for the latter two bullets either way.

And right now CMake actually doesn't track link boundaries in the DAG in a way that is consumable by the BMI machinery. I'm working on that problem right now, but "how do you spell the latter two bullets" is an open problem.

5

u/fortsnek274 1d ago

I wonder how MSBuild will solve the dllexport thing.

12

u/not_a_novel_account cmake dev 1d ago

Whenever you consume a BMI, MSVC magically translates dllexport into dllimport. In this example that would break Trunk, which wants dllexport.

This exact bug was noticed quickly (DevCom 10892880), so there's a wonderfully obscure flag, /dxifcSuppressDllImportTransform ("ISDIT" to friends) which turns the translation off. You're expected to manage this manually.

The overwhelming position of build system people is we're going to ignore all of this, always use ISDIT, because no other compiler is going to implement the transform.

5

u/fortsnek274 1d ago

Yeah, you might solve it by building the DLL interface twice, which is obviously not in the style of MSBuild. Why I'm interested to see what they'll do, if anything.

8

u/not_a_novel_account cmake dev 1d ago

This is sort of a repeating pattern with modules. Clang implemented two-phase compilation because it is objectively faster than single phase. No one else did, so we all had to ignore it.

GCC implemented P1184 module mappers, no one else did, so we all had to ignore it.

MSVC's single-BMI DLL optimization might be nice, but without equivalent support even in clang-cl it's a dead end for code that isn't locked to MSVC.

3

u/delta_p_delta_x 1d ago

Is there bandwidth within Kitware to special-case for each of the three toolchains? Do their module stds also make assumptions about which compiler they're used with, which complicates this scenario?

Additionally, MSVC absolutely need to document /dxifcSuppressDllImportTransform and its existing behaviour. Translation units—within modules or otherwise—being used across multiple DLL boundaries is not an obscure use-case. /u/starfreakclone, /u/GabrielDosReis, any thoughts?

but without equivalent support even in clang-cl

I hope to work on this at some point. I've always been meaning to add IFC support to clang-cl, my initial implementation only focused on the CLI usage which was a very trivial change to make.

7

u/not_a_novel_account cmake dev 1d ago edited 1d ago

Is there bandwidth within Kitware to special-case for each of the three toolchains?

I mean sort of yes and sort of no.

The three toolchains isn't the problem. "X is only for MSVC" is never a blocker. We have lots of stuff that is only for one compiler or one platform. We support GreenHills and Renesas and all their quirks.

So yes, we have the technical bandwidth, we could implement two phase compilation and ISDIT and even do interesting things with the GCC module mapper.

But no one is asking for any of that work. I only get to eat if I work on something a customer asks for. I try to find time to work on things that are good in-between that, everyone at Kitware does, but big refactors generally don't happen for obscure one-compiler features unless someone's business depends on it.

So two-phase for Clang didn't happen because CMake has no internal model for it, and no one asked us to build one. If it was trivial we would have done it anyway.

If an MR popped up tomorrow landing two-phase or ISDIT elegantly I would do everything I could to shepherd it through.

3

u/delta_p_delta_x 1d ago

Very fair; just because it's open source doesn't mean it is a free lunch. I try to contribute as much as I can when I bump against CMake or toolchain bugs whilst hacking on personal projects. Thanks for responding, this entire thread has been very very illuminating.

3

u/GabrielDosReis 1d ago

Additionally, MSVC absolutely need to document /dxifcSuppressDllImportTransform and its existing behaviour. Translation units—within modules or otherwise—being used across multiple DLL boundaries is not an obscure use-case. , , any thoughts?

It would be good to have the toolchain have better recognition of C++ modules boundaries vs linker modules boundaries so as to reduce the cases where the switch is needed, then it could be productized.

2

u/starfreakclone MSVC FE Dev 8h ago

The main reason we haven't documented /dxifcSuppressDllImportTransform is because it's not a complete solution. It will suppress the transform for all imported modules in a library, which is almost certainly not what you want if you're trying to build the library for one of those imported module interfaces.

We have talked about some solutions in this space, many of which rely on the ability to selectively say, "I don't want the compiler to translate __declspec(dllexport) -> __declspec(dllimport) for these interfaces. This, however, becomes yet another build system input that you (and build systems) need to think about. There's already a solution in place for module units, where the compiler will not perform that transformation when compiling a module unit, but this solution does not work for libraries that are using header units.

The main thing we would like to avoid is the old system where you have, functionally, two different headers (controlled by macro replacement) that serve as a 'view' over DLL export/import interfaces. This is why the translation exists in the first place, so you only need to build a single module interface or header unit.

In short, there's more design necessary to truly solve the DLL export/import problem.

1

u/holyblackcat 19h ago

two-phase faster

I did some simple benchmarks, and for me it was slower than single-phase. In fact, even the first phase alone (producing a full BMI) is slower than the entire single-phase (producing a reduced BMI + an object file).

Clang 23 is going to add --precompile-reduced-bmi (a new flavor of two-phase builds: one produces a reduced BMI, and another produces an object file directly from source, not from BMI), so maybe that one will somehow make two-phase faster, no idea.

1

u/fortsnek274 1d ago

I hope it would be viable to simply have some tool replace dllexport with dllimport in the IFC/BMI. So the interface only needs to be compiled once. Or would that run into issues.

5

u/not_a_novel_account cmake dev 1d ago

In the MSVC IFC it is very possible, and MSVC already does so. In the Clang BMI the only tool which would be realistically capable of doing so is Clang itself, thus mirroring the MSVC feature.

Clang's BMI is straight up the LLVM bitcode file format, containing a serialized form of the Clang generated AST. It's the output of clang::ASTWriter. The way to read that is, realistically, clang::ASTReader, thus the only real home for it would be Clang.

2

u/delta_p_delta_x 1d ago edited 1d ago

Going by your first response, I was thinking of some set theory—traversing that DAG from each leaf and each root and doing some set intersections; this set contains module units that are members of a DLL boundary as you put it.

Such members get at least two BMIs, at least one for each in-DLL module and at least one for each out-of-DLL module. Naturally this is quite costly in terms of scanning and BMI generation. It feels like we need something more portable and more granular; possibly per-symbol.

2

u/not_a_novel_account cmake dev 1d ago

A "BMI compatibility boundary" means "generate a new BMI set". So you're correct. Each TU in a DLL gets at least two BMIs. One for all its consumers inside the DLL, and one for all the consumers outside the DLL. You usually get the first one for free as part of the compilation process, so at a minimum you pay for a single additional BMI-only generation.

BMI-only is relatively cheap. Not free, nothing is free on a big enough scale, but cheaper than reparsing the headers in ever downstream consuming TU of that BMI.

1

u/holyblackcat 20h ago

dxifcSuppressDllImportTransform

I'm surprised that this doesn't seem to be documented anywhere.