Skip to content

Add C++ modules support#2291

Merged
yhirose merged 9 commits intoyhirose:masterfrom
mikomikotaishi:master
Feb 2, 2026
Merged

Add C++ modules support#2291
yhirose merged 9 commits intoyhirose:masterfrom
mikomikotaishi:master

Conversation

@mikomikotaishi
Copy link
Contributor

This pull request adds support for C++20 modules through CMake. It is enabled by the HTTPLIB_BUILD_MODULES option (which requires HTTPLIB_COMPILE to be enabled, though it probably doesn't have to - I only forced this requirement because it seems to make the most sense to force the library to compile if modules are to be compiled).

@Spixmaster
Copy link
Contributor

Spixmaster commented Dec 8, 2025

Great work. I also thought about this.

The approach is very similiar to what was done to https://github.com/nlohmann/json with nlohmann/json#4799. Then, I noticed ...

Is this temporary approach with a file with using ... for the meantime while modules and no modules are supported?

This approach is not using modules natively but rather as an interface to the original way.

Does this method work without disadvantages?

@mikomikotaishi
Copy link
Contributor Author

mikomikotaishi commented Dec 8, 2025

This is the best way to my knowledge to support modules on top of a header-only or header/source library, allowing continued support for older versions while providing newer features as an option.

I'm not aware of any disadvantages to it besides a being additional translation unit to compile, but if I am wrong please correct me. The only glaring difference in API is that detail symbols are hidden as they are not exported, but in my opinion that's probably better not to expose detail symbols and flood IDE suggestions with implementation details.

@Spixmaster
Copy link
Contributor

Spixmaster commented Dec 8, 2025

What about compiled libraries? Is it possible to have the traditional method and modules installed in parallel?

I am thinking of repositories that ship compiled .so or .a.

This is relevant here, https://aur.archlinux.org/packages/cpp-httplib-compiled .

@mikomikotaishi
Copy link
Contributor Author

mikomikotaishi commented Dec 8, 2025

Yes I believe it's possible to use shared/static libraries with modules, all of my modular projects compiled to shared libraries that an executable consumes

@yhirose
Copy link
Owner

yhirose commented Dec 8, 2025

@mikomikotaishi, thanks for the fine pull request! It's fantastic, but my concern is that someone needs to update modules/httplib.cppm whenever new external symbols are added or existing symbols are removed/renamed, and it could happen quite often. I am not planning to maintain this file. So if you cannot keep maintaining the file, I am probably not merge it since the file can be easily out-of-date. Is there a way to generate modules/httplib.cppm from httplib.h automatically?

@sum01 @jimmy-park @Tachi107 do you have any thought about this pull request?

@mikomikotaishi
Copy link
Contributor Author

mikomikotaishi commented Dec 8, 2025

I could create a Python script, or some other kinds of automated means of updating, which you could run every time it is updated. Until then I would be OK with maintaining this file, as it is a simple process. Such a script would probably comb through the file and add any symbols not part of a detail or internal namespace, or prefixed with an underscore, etc.

However, I am curious why it is not feasible to update the file manually. In case it isn't clear how, one can update the file by adding a using httplib::NEW_SYMBOL_HERE; into the export namespace httlib { } block, for each new symbol that is added. Do let me know if any more information is needed.

@mikomikotaishi
Copy link
Contributor Author

I have also seen some repositories use bots to push some commits too. Potentially one such bot could be set up to automatically populate the module with new changes each time there is a mismatch. I don't know anything about how to set this up, but I have seen this before and it could potentially be a solution (but I think the simplest one is just to run a Python script each time any update to the library happens).

@mikomikotaishi
Copy link
Contributor Author

Anyway, I think this could be one such way of automatically updating the module.

@yhirose
Copy link
Owner

yhirose commented Dec 9, 2025

@mikomikotaishi thanks for the additional explanation. I am ok with the following your suggestion.

I have also seen some repositories use bots to push some commits too. Potentially one such bot could be set up to automatically populate the module with new changes each time there is a mismatch. I don't know anything about how to set this up, but I have seen this before and it could potentially be a solution.

We could automatically generate modules/httplib.cppm in a GitHub Actions script when httplib.h is pushed. (I can't accept the way to run a script to update the file manually, since I don't maintain any build systems in this repository. Please see #955 (comment).)

@mikomikotaishi
Copy link
Contributor Author

OK, that makes sense to me. (I don't know anything about how to run GitHub Actions or write scripts for it however, so I'm afraid the most I can do is create a script for this.)

@mikomikotaishi
Copy link
Contributor Author

I'm not sure why there were failing workflows as I didn't change anything in the core library

@mikomikotaishi
Copy link
Contributor Author

Never mind, it seems the failing CI is happening upstream too.

@Tachi107
Copy link
Contributor

Tachi107 commented Dec 9, 2025 via email

@mikomikotaishi
Copy link
Contributor Author

mikomikotaishi commented Dec 9, 2025

@Tachi107 CMake needs to know what the output directory is ahead of time to compile the module. How do you propose to solve this?

@mikomikotaishi
Copy link
Contributor Author

@yhirose I think there is one possible solution to allow both the directory to be user-specified while still supporting CMake module building, which is probably just to have the Python script generate the CMake file too. I don't know if this is too convoluted or awkward of a design though, so please do tell me your thoughts.

@Tachi107
Copy link
Contributor

Tachi107 commented Dec 11, 2025 via email

Copy link
Contributor

@Tachi107 Tachi107 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some feedback now that I have been able to look at the whole code :)

@sum01
Copy link
Contributor

sum01 commented Dec 14, 2025

Is there a reason to try and add module support when the library itself is C++11?

Not to mention, Cmake v3.28 adds built-in module support, which isn't being used either.

@Spixmaster
Copy link
Contributor

Spixmaster commented Dec 14, 2025

This library being C++11 does not require programmes depending on it being C++11.

I support the modules.

@mikomikotaishi
Copy link
Contributor Author

Indeed, some people who use C++20 and onward may wish to consume the library with the module rather than the header. This is precisely what modules are designed to improve for compilations, as re-including the same large header in multiple locations massively increases build times whereas modules are built only once and then imported/linked.

@sum01
Copy link
Contributor

sum01 commented Dec 14, 2025

I understand you can use older standards on newer ones, but I mean trying to add support where it isn't "supported" seems unnecessary.

As for having to re-compile, why not use pre-compiled headers (Cmake v3.16 feature), or the compiled version of this library?

@mikomikotaishi
Copy link
Contributor Author

mikomikotaishi commented Dec 14, 2025

Precompiled headers are not a standard language feature, they are just a compiler trick. Modules provide a very specific API and very specific features for library encapsulation. For example, we may want to avoid including httplib's macros or avoid adding detail/implementation symbols into scope, which overwhelms autocomplete on IDEs with irrelevant symbols.

Even splitting the library into a header/source division still leaves a very large header that gets recompiled each time.

As for the question of "support", this specific wrapper style (a module which includes the header and then re-exports the symbols) is how most libraries built with C++11/14/17 provide module support (such as Boost, Poco, etc.) This is the simplest way to do it to my knowledge, and it's not a hastily tacked-on hack. You can see in other libraries this is how originally header-libraries are wrapped for modules.

@mikomikotaishi
Copy link
Contributor Author

Hi, sorry for the silence on this issue. I've been busy with an exam, but I'll return to this and send a solution today or tomorrow.

@mikomikotaishi
Copy link
Contributor Author

@yhirose Hi, I am sorry but I am completely lost as to how to actually generate a module correctly from the header. I was considering using libclang but I don't want to introduce a new dependency for a build script.

@mikomikotaishi
Copy link
Contributor Author

I personally would rather manually maintain the module file for the time being, but if someone more skilled at parsing C++ wants to take a crack at this please feel free.

@yhirose
Copy link
Owner

yhirose commented Dec 29, 2025

@mikomikotaishi Thanks for your willingness. However, I am now thinking of closing this pull request for the maintenance reason I commented on before. I would like to confirm that you will commit to updating modules/httplib.cppm whenever new external symbols are added or existing symbols are removed/renamed by contributors. I am not going to wait for this update to happen. This update must be done by you before I release a new version. Otherwise, httplib.h will be shipped with an incompatible, out-of-date httplib.cppm. If you accept this responsibility from now on, I will reconsider it.

@mikomikotaishi
Copy link
Contributor Author

@yhirose Could you please review the current changes and give your feedback on the current state?

@yhirose
Copy link
Owner

yhirose commented Feb 1, 2026

Thank you for all your hard work! I just tried to build with g++-15 installed from homebrew on macos. I made the following test file.

// test-cpp-modules.cc
import httplib;
using namespace httplib;

int main() {
  Server svr;
  svr.Get("/hi", [](const Request & /*req*/, Response &res) {
    res.set_content("Hello World!", "text/plain");
  });
  svr.listen("0.0.0.0", 8080);
}

Then, I did the following:

> g++-15 -std=c++20 -fmodules-ts -x c++ -c httplib.cppm
> g++-15 -std=c++20 -fmodules-ts test-cpp-modules.cc httplib.o -o test-cpp-modules

But when I run the second command to make the executable file, the compiler gave me errors below...

[mikomikotaishi-master] ~/Projects/cpp-httplib/test$ g++-15 -std=c++20 -fmodules-ts test-cpp-modules.cc httplib.o -o test-cpp-modules
In file included from /opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/functional:61,
                 from httplib.cppm:106,
of module httplib, imported at test-cpp-modules.cc:5:
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/std_function.h: In instantiation of 'static bool std::_Function_handler<_Res(_ArgTypes ...), _Functor>::_M_manager(std::_Any_data&, const std::_Any_data&, std::_Manager_operation) [with _Res = void; _Functor = main()::<lambda(const httplib::Request@httplib&, httplib::Response@httplib&)>; _ArgTypes = {const httplib::Request@httplib&, httplib::Response@httplib&}]':
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/std_function.h:454:21:   required from 'std::function<_Res(_ArgTypes ...)>::function(_Functor&&) [with _Functor = main()::<lambda(const httplib::Request@httplib&, httplib::Response@httplib&)>; _Constraints = void; _Res = void; _ArgTypes = {const httplib::Request@httplib&, httplib::Response@httplib&}]'
  454 |               _M_manager = &_My_handler::_M_manager;
      |                            ^~~~~~~~~~~~~~~~~~~~~~~~
test-cpp-modules.cc:11:18:   required from here
   11 |   svr.Get("/hi", [](const Request & /*req*/, Response &res) {
      |                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   12 |     res.set_content("Hello World!", "text/plain");
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   13 |   });
      |   ~
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/std_function.h:276:53: error: must '#include <typeinfo>' before using 'typeid'
   69 |    */
  +++ |+#include <typeinfo>
   70 |   template<typename _Tp>
......
  276 |             __dest._M_access<const type_info*>() = &typeid(_Functor);
      |                                                     ^~~~~~~~~~~~~~~~
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/std_function.h: In instantiation of 'static void std::_Function_base::_Base_manager<_Functor>::_M_create(std::_Any_data&, _Fn&&, std::true_type) [with _Fn = main()::<lambda(const httplib::Request@httplib&, httplib::Response@httplib&)>; _Functor = main()::<lambda(const httplib::Request@httplib&, httplib::Response@httplib&)>; std::true_type = std::true_type]':
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/std_function.h:217:15:   required from 'static void std::_Function_base::_Base_manager<_Functor>::_M_init_functor(std::_Any_data&, _Fn&&) [with _Fn = main()::<lambda(const httplib::Request@httplib&, httplib::Response@httplib&)>; _Functor = main()::<lambda(const httplib::Request@httplib&, httplib::Response@httplib&)>]'
  217 |             _M_create(__functor, std::forward<_Fn>(__f), _Local_storage());
      |             ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/std_function.h:451:36:   required from 'std::function<_Res(_ArgTypes ...)>::function(_Functor&&) [with _Functor = main()::<lambda(const httplib::Request@httplib&, httplib::Response@httplib&)>; _Constraints = void; _Res = void; _ArgTypes = {const httplib::Request@httplib&, httplib::Response@httplib&}]'
  451 |               _My_handler::_M_init_functor(_M_functor,
      |               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~
  452 |                                            std::forward<_Functor>(__f));
      |                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test-cpp-modules.cc:11:18:   required from here
   11 |   svr.Get("/hi", [](const Request & /*req*/, Response &res) {
      |                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   12 |     res.set_content("Hello World!", "text/plain");
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   13 |   });
      |   ~
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/std_function.h:154:13: error: no matching function for call to 'operator new(sizetype, void*)'
  154 |             ::new (__dest._M_access()) _Functor(std::forward<_Fn>(__f));
      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/std_function.h:154:13: note: there are 2 candidates
In file included from /opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/stl_iterator.h:75,
                 from /opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/stl_algobase.h:67,
                 from /opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/algorithm:62,
                 from httplib.cppm:94,
of module httplib, imported at test-cpp-modules.cc:5:
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/new:137:26: note: candidate 1: 'void* operator new(std::size_t)'
  137 | _GLIBCXX_NODISCARD void* operator new(std::size_t)
      |                          ^~~~~~~~
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/new:137:26: note: candidate expects 1 argument, 2 provided
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/new:168:26: note: candidate 2: 'void* operator new(std::size_t, std::align_val_t)'
  168 | _GLIBCXX_NODISCARD void* operator new(std::size_t, std::align_val_t)
      |                          ^~~~~~~~
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/new:168:26: note: no known conversion for argument 2 from 'void*' to 'std::align_val_t'

Do you have any clue?

Co-authored-by: Andrea Pappacoda <andrea@pappacoda.it>
@mikomikotaishi
Copy link
Contributor Author

It looks like the problem is that GCC has a broken module implementation. Adding

#include <new>
#include <typeinfo>

inside your test-cpp-modules.cc file fixes it on GCC.

In my experience there is no need to include those headers on Clang; that's why I'm convinced it's a GCC bug.

@yhirose
Copy link
Owner

yhirose commented Feb 1, 2026

If I put the two lines before import httplib, I got the followings:

Undefined symbols for architecture arm64:
  "httplib::detail::BufferStream@httplib::read(char*, unsigned long)", referenced from:
      vtable for httplib::detail::BufferStream@httplib in httplib.o
  "httplib::detail::BufferStream@httplib::write(char const*, unsigned long)", referenced from:
      vtable for httplib::detail::BufferStream@httplib in httplib.o
  "httplib::detail::close_socket@httplib(int)", referenced from:
      httplib::ClientConnection@httplib::~ClientConnection() in httplib.o
      httplib::ClientConnection@httplib::~ClientConnection() in httplib.o
  "httplib::detail::nocompressor@httplib::compress(char const*, unsigned long, bool, std::function<bool (char const*, unsigned long)>)", referenced from:
      vtable for httplib::detail::nocompressor@httplib in httplib.o
  "httplib::ClientImpl@httplib::StreamHandle::read(char*, unsigned long)", referenced from:
      httplib::stream::Result@httplib::next() in httplib.o
  "httplib::ClientImpl@httplib::shutdown_ssl(httplib::ClientImpl@httplib::Socket&, bool)", referenced from:
      vtable for httplib::ClientImpl@httplib in httplib.o
  "httplib::ClientImpl@httplib::process_socket(httplib::ClientImpl@httplib::Socket const&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1l, 1000000000l>>>, std::function<bool (httplib::Stream@httplib&)>)", referenced from:
      vtable for httplib::ClientImpl@httplib in httplib.o
  "httplib::ClientImpl@httplib::ensure_socket_connection(httplib::ClientImpl@httplib::Socket&, httplib::Error@httplib&)", referenced from:
      vtable for httplib::ClientImpl@httplib in httplib.o
  "httplib::ClientImpl@httplib::create_and_connect_socket(httplib::ClientImpl@httplib::Socket&, httplib::Error@httplib&)", referenced from:
      vtable for httplib::ClientImpl@httplib in httplib.o
  "httplib::ClientImpl@httplib::~ClientImpl()", referenced from:
      vtable for httplib::ClientImpl@httplib in httplib.o
  "httplib::ClientImpl@httplib::~ClientImpl()", referenced from:
      vtable for httplib::ClientImpl@httplib in httplib.o
  "httplib::Client@httplib::open_stream(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::multimap<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>>> const&, std::unordered_multimap<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, httplib::detail::case_ignore::hash@httplib, httplib::detail::case_ignore::equal_to@httplib, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)", referenced from:
      httplib::stream::Result@httplib httplib::stream::Get@httplib<httplib::Client@httplib>(httplib::Client@httplib&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::unordered_multimap<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, httplib::detail::case_ignore::hash@httplib, httplib::detail::case_ignore::equal_to@httplib, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>>> const&, unsigned long) in httplib.o
  "httplib::Client@httplib::stop()", referenced from:
      httplib::sse::SSEClient@httplib::stop() in httplib.o
  "httplib::Server@httplib::process_and_close_socket(int)", referenced from:
      vtable for httplib::Server@httplib in httplib.o
  "httplib::Server@httplib::Get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::function<void (httplib::Request@httplib const&, httplib::Response@httplib&)>)", referenced from:
      _main in cc5namgh.o
  "httplib::Server@httplib::listen(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, int, int)", referenced from:
      _main in cc5namgh.o
  "httplib::Server@httplib::Server()", referenced from:
      _main in cc5namgh.o
  "httplib::Server@httplib::~Server()", referenced from:
      vtable for httplib::Server@httplib in httplib.o
  "httplib::Server@httplib::~Server()", referenced from:
      _main in cc5namgh.o
      _main in cc5namgh.o
      vtable for httplib::Server@httplib in httplib.o
  "httplib::Response@httplib::set_content(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)", referenced from:
      main::'lambda'(httplib::Request@httplib const&, httplib::Response@httplib&)::operator()(httplib::Request@httplib const&, httplib::Response@httplib&) const in cc5namgh.o
  "httplib::detail::BufferStream@httplib::is_readable() const", referenced from:
      vtable for httplib::detail::BufferStream@httplib in httplib.o
  "httplib::detail::BufferStream@httplib::wait_readable() const", referenced from:
      vtable for httplib::detail::BufferStream@httplib in httplib.o
  "httplib::detail::BufferStream@httplib::wait_writable() const", referenced from:
      vtable for httplib::detail::BufferStream@httplib in httplib.o
  "httplib::detail::BufferStream@httplib::get_local_ip_and_port(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>&, int&) const", referenced from:
      vtable for httplib::detail::BufferStream@httplib in httplib.o
  "httplib::detail::BufferStream@httplib::get_remote_ip_and_port(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>&, int&) const", referenced from:
      vtable for httplib::detail::BufferStream@httplib in httplib.o
  "httplib::detail::BufferStream@httplib::socket() const", referenced from:
      vtable for httplib::detail::BufferStream@httplib in httplib.o
  "httplib::detail::BufferStream@httplib::duration() const", referenced from:
      vtable for httplib::detail::BufferStream@httplib in httplib.o
  "httplib::detail::RegexMatcher@httplib::match(httplib::Request@httplib&) const", referenced from:
      vtable for httplib::detail::RegexMatcher@httplib in httplib.o
  "httplib::detail::PathParamsMatcher@httplib::match(httplib::Request@httplib&) const", referenced from:
      vtable for httplib::detail::PathParamsMatcher@httplib in httplib.o
  "httplib::ClientImpl@httplib::is_ssl() const", referenced from:
      vtable for httplib::ClientImpl@httplib in httplib.o
  "httplib::ClientImpl@httplib::is_valid() const", referenced from:
      vtable for httplib::ClientImpl@httplib in httplib.o
  "httplib::Server@httplib::is_valid() const", referenced from:
      vtable for httplib::Server@httplib in httplib.o
  "httplib::Response@httplib::has_header(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) const", referenced from:
      httplib::stream::Result@httplib::has_header(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) const in httplib.o
  "httplib::Response@httplib::get_header_value(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, char const*, unsigned long) const", referenced from:
      httplib::stream::Result@httplib::get_header_value(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, char const*) const in httplib.o
ld: symbol(s) not found for architecture arm64
collect2: error: ld returned 1 exit status

When the lines are added after import httplib;, I got this error. So the workaround didn't work...

In file included from /opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/new:43,
                 from test-cpp-modules.cc:7:
/opt/homebrew/Cellar/gcc/15.2.0/include/c++/15/bits/exception.h:64:36: internal compiler error: in finish_member_declaration, at cp/semantics.cc:4210
   64 |     exception() _GLIBCXX_NOTHROW { }
      |                                    ^
Please submit a full bug report, with preprocessed source (by using -freport-bug).
See <https://github.com/Homebrew/homebrew-core/issues> for instructions.

@yhirose
Copy link
Owner

yhirose commented Feb 1, 2026

Also I confirmed that the latest Apple Clang in Xcode 26.2 doesn't work. So I don't know how I can apply this C++ module support on my mac...

@mikomikotaishi
Copy link
Contributor Author

mikomikotaishi commented Feb 1, 2026

Did you make sure to link back to the source file, which contains the implementations of most of these listed symbols? I had a similar error until I realised I had not done the aforementioned step.

@yhirose
Copy link
Owner

yhirose commented Feb 1, 2026

Could you please try the following with g++-15 on your machine to see if you get the same error or not? If you get the same error, could you show me how you can fix the problem? Thanks.

g++-15 -std=c++20 -fmodules-ts -x c++ -c httplib.cppm
g++-15 -std=c++20 -fmodules-ts test-cpp-modules.cc httplib.o -o test-cpp-modules

// test-cpp-modules.cc
import httplib;

#include <new>
#include <typeinfo>

using namespace httplib;

int main() {
  Server svr;
  svr.Get("/hi", [](const Request & /*req*/, Response &res) {
    res.set_content("Hello World!", "text/plain");
  });
  svr.listen("0.0.0.0", 8080);
}

@mikomikotaishi
Copy link
Contributor Author

This seemed to work for me:

$ g++ -std=c++20 -c httplib.cc -o httplib.o
$ g++ -std=c++20 -fmodules-ts -x c++ -c httplib.cppm -o httplib_module.o
$ g++ -std=c++20 -fmodules-ts test.cpp httplib.o httplib_module.o -o test

Also, if you are getting a compiler crash or internal error on GCC, move the #includes before import httplib;.

@yhirose
Copy link
Owner

yhirose commented Feb 1, 2026

Sorry, but the following still gives me the same exact errors regardless whether #include is located before or after import httplib;. Did you really try what I asked you to do?

g++-15 -std=c++20 -c httplib.cc -o httplib.o
g++-15 -std=c++20 -fmodules-ts -x c++ -c httplib.cppm -o httplib_module.o
g++-15 -std=c++20 -fmodules-ts test-cpp-modules.cc httplib.o httplib_module.o -o test-cpp-modules

@mikomikotaishi
Copy link
Contributor Author

mikomikotaishi commented Feb 1, 2026

Have you cloned the most recent revision? Specifically, the generated module should have export extern "C++":

export extern "C++" {
    #include "httplib.h"
}

Lacking extern "C++" seems to cause a similar error to what you showed, but when it is present it builds successfully. It is specifically for marking the module as an interface to the header, i.e. symbols in module httplib refer to the same symbols you would get from including <httplib.h>

@yhirose
Copy link
Owner

yhirose commented Feb 1, 2026

I am using the latest split.py in this pull request to generate httplib.cppm, and it includes extern "C++" at the end.

...

export module httplib;

export {
    #include "httplib.h"
}

@mikomikotaishi
Copy link
Contributor Author

I'm confused what you mean, you say it includes extern "C++" but the snippet you posted doesn't have extern "C++".

@yhirose
Copy link
Owner

yhirose commented Feb 2, 2026

You are right. I'll clone the latest. Sorry for the confusion.

@yhirose
Copy link
Owner

yhirose commented Feb 2, 2026

I finally got it to build with C++ modules. Thanks for your help. (#include <new> and #include <typeinfo> should be added before import httplib;.)

#include <new>
#include <typeinfo>

import httplib;

using namespace httplib;

int main() {
  Server svr;

  svr.Get("/hi", [](const Request & /*req*/, Response &res) {
    res.set_content("Hello World!", "text/plain");
  });

  svr.listen("0.0.0.0", 8080);
}

@mikomikotaishi
Copy link
Contributor Author

Fantastic!

Is there anything left to do before we can finish this PR?

@yhirose
Copy link
Owner

yhirose commented Feb 2, 2026

I just took a look at the code, and I realized that the C++ module generation isn't related to split.py. It would be better to move this functionality to a separate file, such as gen_cpp_module.py. So, could you please move the module generation code to gen_cpp_module.py and revert the changes to split.py? That's all!

@mikomikotaishi
Copy link
Contributor Author

But I think a lot of the logic that is used to construct the module is dependent on the code splitting script, and it's simpler to ensure the module generation takes place after the header is split into header and source, etc. I just personally don't think that there is much to gain from dividing the logic, especially if module generation is only allowed when the library is compiled, etc.

@yhirose
Copy link
Owner

yhirose commented Feb 2, 2026

It's fine with me if gen_cpp_module.py gets longer. My main intention is to keep this additional feature separate from the official files that I maintain, such as split.py. Thanks for your understanding.

@mikomikotaishi
Copy link
Contributor Author

@yhirose I split the script into two files and tested it again with both manually calling the script and doing it from CMake, and it looks good to me.

@yhirose
Copy link
Owner

yhirose commented Feb 2, 2026

Looks good to me. Thanks for all your hard work!

@yhirose yhirose merged commit 1942e0e into yhirose:master Feb 2, 2026
11 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants