Since #4718 removed the private module fragment from src/fmt.cc, the module no longer works with MSVC 19.51 (Visual Studio 2026 18.11) as soon as an importer instantiates floating-point formatting, e.g. fmt::to_string(double) (the same call as in module_test.to_string) or a compiled format string such as "{:.3f}"_cf. Runtime format strings (fmt::format("{:.3f}", x)) and integer compiled formats are not affected.
Repro on current master (0fc3163) with the documented CMake setup, from an x64 VS 2026 developer prompt (Windows x64, CMake 4.4.4, Ninja 1.14):
CMakeLists.txt (next to a fmt checkout):
cmake_minimum_required(VERSION 3.28)
project(repro CXX)
set(CMAKE_CXX_STANDARD 20)
add_subdirectory(fmt)
add_executable(repro main.cc)
target_link_libraries(repro PRIVATE fmt::fmt-module)main.cc:
import fmt;
int main(int argc, char**) {
double value = argc;
fmt::print("{}\n", fmt::to_string(value));
}cmake -B build -G Ninja -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Debug
cmake --build buildOutput (translated from a localized compiler; codes and locations are verbatim):
...\fmt\include\fmt\format-inl.h(1278): error C2065: 'T': undeclared identifier
...\fmt\include\fmt\format-inl.h(1278): error C2923: 'fmt::v12::detail::dragonbox::float_info': 'T' is not a valid template type argument for parameter 'T'
...\fmt\include\fmt\format-inl.h(1278): error C2976: 'fmt::v12::detail::dragonbox::float_info': too few template argumentsLine 1278 is the first statement of dragonbox::to_decimal. The same happens with C++23 and in Release.
Results for fmt::to_string(double) and "{:.3f}"_cf (- = not tested):
| {fmt} | MSVC 19.51.36260 | MSVC 19.44.35229 | clang-cl 23.1.3 |
|---|---|---|---|
| 907f002 (parent of #4718) | OK | OK | - |
| 3febdca (#4718), 12.2.0 | C2065 | - | - |
| 0fc3163 (master) | C2065 | OK | OK |
master + module :private; | OK | OK | OK |
Since MSVC 19.44 (VS 2022, which the Windows CI uses) and Clang work, this looks like an MSVC 19.51 regression that the private module fragment used to hide. Reduced repro without {fmt} (fails with 19.51, compiles with 19.44):
// m.cc
export module m;
template <typename T> T f(T x) {
T y = x;
return y;
}
export template <typename T> T g(T x) { return f(x); }
template double f(double);// main.cc
import m;
int main(int argc, char**) { return static_cast<int>(g(argc * 1.0)) - 1; }cl /std:c++20 /c /interface /TP m.cc
cl /std:c++20 /c main.cc /reference m=m.ifcIn my testing it only fails when the importer instantiates a template (g) that uses a specialization explicitly instantiated in the interface (f<double>, like to_decimal in format.cc). Removing the explicit instantiation, instantiating a different type, or calling f directly from the importer avoids it; an extern template declaration doesn't. Moving the definition and the explicit instantiation into a private module fragment (the pre-#4718 layout) also avoids it.
m.cc(4): error C2065: 'T': undeclared identifier
It only fails when the importer instantiates a template (`g`) that uses a specialization explicitly instantiated in the interface (`f<double>`, like `to_decimal` in `format.cc`). Removing the explicit instantiation, instantiating a different type, or calling `f` directly from the importer avoids it; an `extern template` declaration doesn't. Moving the definition and the explicit instantiation into a private module fragment (the pre-#4718 layout) also avoids it.
Restoring `module :private;` unconditionally, as suggested in the #4718 review, fixes MSVC 19.51 and still works with MSVC 19.44 and Clang 23, but GCC doesn't implement private module fragments yet and the directive can't be conditional. Would you consider restoring it for compilers that support it (e.g. with a separate module source for GCC), or would you rather wait for an MSVC fix?Activity
3Could you provide a godbolt repro?
Thanks. Here is a Godbolt project with the reduced two-file module example and MSVC 19.51 selected: https://godbolt.org/z/ved6a38KG. It uses separate cl /interface and importer /reference invocations for m.cc and main.cc. I could not verify the online compiler result because Compiler Explorer's compile and CMake APIs returned HTTP 502 during my check, so I can't claim that Godbolt produced the C2065 diagnostic here.
I can also reproduce this with MSVC 19.50.35718 (x64), so the affected range appears to include 19.50, not only 19.51.
On current
0fc31633e7d53ac895ae0a688b83b7d44f0b0c1f, compilingsrc/fmt.ccas a C++20 interface succeeds, then compiling yourimport fmt;/fmt::to_string(double)importer fails atformat-inl.h(1278)with C2065/C2923/C2976. I used/utf-8 /EHsc /std:c++20, compiled the interface with/c /interface /TP, and referenced the resultingfmt.ifcexplicitly when compiling the importer. Your standalonem.cc/main.ccreduced example also reproduces C2065 on this compiler.Skipping explicit instantiations alone did not produce a working workaround on 19.50:
dragonbox::to_decimalexplicit instantiations moves the error todecimal_point_impl(Charundeclared atformat-inl.h(140)). Omitting the explicit instantiations informat.ccaltogether still gives that error.Correction to my initial experiment: restoring
module :private;immediately before the implementation includes does allow the importer to compile, link and print1on 19.50. The initial copied-source experiment accidentally lacked the directive; I caught and corrected that local experiment. I have not tested 19.51.Merely dropping the Dragonbox instantiations is not sufficient on this version. I am checking a CMake-generated MSVC module source with an unconditional private fragment, leaving the GCC/Clang source unchanged to avoid reintroducing #4718's conditional-directive problem.