From 9c5b66e0be3eb57d36612b213e15dcc61a92632e Mon Sep 17 00:00:00 2001 From: kydos Date: Fri, 29 May 2026 08:36:33 +0200 Subject: [PATCH 1/3] Add ASIO_CPO_INLINE_CONSTEXPR for C++20 named-module compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC 15+ (and Clang in strict module mode) reject headers included in a named module's global module fragment when those headers declare CPO variables in anonymous namespaces — the variables have internal linkage (TU-local), which violates the C++20 module rules for exported entities that reference them transitively through template constraints. Introduce ASIO_CPO_INLINE_CONSTEXPR (in detail/config.hpp) as an opt-in flag that replaces the anonymous-namespace singleton pattern in the four affected CPO headers with `inline constexpr impl cpo{}`. This is semantically equivalent: the impl types are stateless function objects with constexpr constructors, and inline variables have the same ODR guarantee (one definition across all TUs) as the static_instance<> scheme. The flag is compiler-neutral. GCC named-module builds activate it via -DASIO_GCC_NAMED_MODULES; other compilers may set ASIO_CPO_INLINE_CONSTEXPR directly. Existing builds are unaffected: the flag defaults to off and the original anonymous-namespace path is preserved under #else. Files changed: asio/include/asio/detail/config.hpp — macro definition and detection asio/include/asio/prefer.hpp — prefer CPO asio/include/asio/require.hpp — require CPO asio/include/asio/query.hpp — query CPO asio/include/asio/require_concept.hpp — require_concept CPO --- include/asio/detail/config.hpp | 20 ++++++++++++++++++++ include/asio/prefer.hpp | 4 ++++ include/asio/query.hpp | 4 ++++ include/asio/require.hpp | 4 ++++ include/asio/require_concept.hpp | 4 ++++ 5 files changed, 36 insertions(+) diff --git a/include/asio/detail/config.hpp b/include/asio/detail/config.hpp index 1172b870cb..14f0517dc0 100644 --- a/include/asio/detail/config.hpp +++ b/include/asio/detail/config.hpp @@ -1658,4 +1658,24 @@ # define ASIO_VERSIONED_NAME(name) asio_ ## name #endif // defined(ASIO_VERSION_NAMESPACE) +// Named C++20 modules require CPOs (customization point objects) to have +// external linkage. The anonymous-namespace singleton idiom used by asio's +// prefer/require/query/require_concept CPOs creates TU-local entities that +// GCC 15+ (and future Clang in strict mode) reject when those headers are +// #included inside a named module's global module fragment. +// +// Define ASIO_CPO_INLINE_CONSTEXPR to replace the anonymous-namespace +// reference pattern with an inline constexpr variable. This is safe for all +// compilers: the impl types are stateless function objects with constexpr +// constructors, so `inline constexpr impl cpo{}` has identical ODR semantics. +// This flag is compiler-neutral; enable it for any C++20 named-module build. +#if !defined(ASIO_CPO_INLINE_CONSTEXPR) \ + && !defined(ASIO_DISABLE_CPO_INLINE_CONSTEXPR) +// Automatically activate when the user opts in via a build-system flag. +// In GCC named-module builds, pass -DASIO_GCC_NAMED_MODULES to trigger this. +# if defined(ASIO_GCC_NAMED_MODULES) +# define ASIO_CPO_INLINE_CONSTEXPR 1 +# endif // defined(ASIO_GCC_NAMED_MODULES) +#endif // !defined(ASIO_CPO_INLINE_CONSTEXPR) + #endif // ASIO_DETAIL_CONFIG_HPP diff --git a/include/asio/prefer.hpp b/include/asio/prefer.hpp index 48890a14c8..af42d52f32 100644 --- a/include/asio/prefer.hpp +++ b/include/asio/prefer.hpp @@ -520,12 +520,16 @@ const T static_instance::instance = {}; } // namespace ASIO_VERSIONED_NAME(prefer_fn) namespace asio { ASIO_INLINE_NAMESPACE_BEGIN +#if defined(ASIO_CPO_INLINE_CONSTEXPR) +inline constexpr ASIO_VERSIONED_NAME(prefer_fn)::impl prefer{}; +#else // defined(ASIO_CPO_INLINE_CONSTEXPR) namespace { static constexpr const ASIO_VERSIONED_NAME(prefer_fn)::impl& prefer = ASIO_VERSIONED_NAME(prefer_fn)::static_instance<>::instance; } // namespace +#endif // defined(ASIO_CPO_INLINE_CONSTEXPR) typedef ASIO_VERSIONED_NAME(prefer_fn)::impl prefer_t; diff --git a/include/asio/query.hpp b/include/asio/query.hpp index ebb1791db4..051e191433 100644 --- a/include/asio/query.hpp +++ b/include/asio/query.hpp @@ -257,12 +257,16 @@ const T static_instance::instance = {}; } // namespace ASIO_VERSIONED_NAME(query_fn) namespace asio { ASIO_INLINE_NAMESPACE_BEGIN +#if defined(ASIO_CPO_INLINE_CONSTEXPR) +inline constexpr ASIO_VERSIONED_NAME(query_fn)::impl query{}; +#else // defined(ASIO_CPO_INLINE_CONSTEXPR) namespace { static constexpr const ASIO_VERSIONED_NAME(query_fn)::impl& query = ASIO_VERSIONED_NAME(query_fn)::static_instance<>::instance; } // namespace +#endif // defined(ASIO_CPO_INLINE_CONSTEXPR) typedef ASIO_VERSIONED_NAME(query_fn)::impl query_t; diff --git a/include/asio/require.hpp b/include/asio/require.hpp index 3a1029b2a3..78884883ee 100644 --- a/include/asio/require.hpp +++ b/include/asio/require.hpp @@ -375,12 +375,16 @@ const T static_instance::instance = {}; } // namespace ASIO_VERSIONED_NAME(require_fn) namespace asio { ASIO_INLINE_NAMESPACE_BEGIN +#if defined(ASIO_CPO_INLINE_CONSTEXPR) +inline constexpr ASIO_VERSIONED_NAME(require_fn)::impl require{}; +#else // defined(ASIO_CPO_INLINE_CONSTEXPR) namespace { static constexpr const ASIO_VERSIONED_NAME(require_fn)::impl& require = ASIO_VERSIONED_NAME(require_fn)::static_instance<>::instance; } // namespace +#endif // defined(ASIO_CPO_INLINE_CONSTEXPR) typedef ASIO_VERSIONED_NAME(require_fn)::impl require_t; diff --git a/include/asio/require_concept.hpp b/include/asio/require_concept.hpp index d1ba31654e..3befd3b286 100644 --- a/include/asio/require_concept.hpp +++ b/include/asio/require_concept.hpp @@ -285,6 +285,9 @@ const T static_instance::instance = {}; } // namespace ASIO_VERSIONED_NAME(require_concept_fn) namespace asio { ASIO_INLINE_NAMESPACE_BEGIN +#if defined(ASIO_CPO_INLINE_CONSTEXPR) +inline constexpr ASIO_VERSIONED_NAME(require_concept_fn)::impl require_concept{}; +#else // defined(ASIO_CPO_INLINE_CONSTEXPR) namespace { static constexpr const ASIO_VERSIONED_NAME(require_concept_fn)::impl& @@ -292,6 +295,7 @@ static constexpr const ASIO_VERSIONED_NAME(require_concept_fn)::impl& require_concept_fn)::static_instance<>::instance; } // namespace +#endif // defined(ASIO_CPO_INLINE_CONSTEXPR) typedef ASIO_VERSIONED_NAME(require_concept_fn)::impl require_concept_t; From 92d6c4a6afd9a210dbe16a813390cc77587f7144 Mon Sep 17 00:00:00 2001 From: kydos Date: Fri, 29 May 2026 08:39:52 +0200 Subject: [PATCH 2/3] Add ASIO_DETAIL_CONSTEXPR_VAR for namespace-scope const variables Namespace-scope `const` variables in C++ have internal linkage by default. When asio/detail/socket_types.hpp is included in a C++20 named module's global module fragment, these variables become TU-local entities; GCC 15+ rejects module functions that reference them (e.g. reactive_socket_service::accept, basic_socket_streambuf:: connect_to_endpoints, reactive_socket_accept_op::do_perform). Introduce ASIO_DETAIL_CONSTEXPR_VAR in detail/config.hpp: - When ASIO_CPO_INLINE_CONSTEXPR is set: expands to `inline constexpr`, giving these variables external linkage (C++17 inline variable). - Otherwise: expands to `const`, preserving the existing behaviour. Apply the macro to all namespace-scope integer constants in socket_types.hpp across all platform branches (Windows Runtime, Windows/Cygwin, and POSIX), plus the three cross-platform constants (custom_socket_option_level, enable_connection_aborted_option, always_fail_option). --- include/asio/detail/config.hpp | 12 ++++++++++ include/asio/detail/socket_types.hpp | 36 ++++++++++++++-------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/include/asio/detail/config.hpp b/include/asio/detail/config.hpp index 14f0517dc0..84270ff943 100644 --- a/include/asio/detail/config.hpp +++ b/include/asio/detail/config.hpp @@ -1658,6 +1658,18 @@ # define ASIO_VERSIONED_NAME(name) asio_ ## name #endif // defined(ASIO_VERSION_NAMESPACE) +// Namespace-scope `const` variables (non-inline) have internal linkage in +// C++. When included in a named module's global module fragment they become +// TU-local entities; GCC 15+ rejects module functions that reference them. +// ASIO_DETAIL_CONSTEXPR_VAR expands to `inline constexpr` under +// ASIO_CPO_INLINE_CONSTEXPR, giving these variables external linkage and +// making them safe to reference from named modules. +#if defined(ASIO_CPO_INLINE_CONSTEXPR) +# define ASIO_DETAIL_CONSTEXPR_VAR inline constexpr +#else +# define ASIO_DETAIL_CONSTEXPR_VAR const +#endif // defined(ASIO_CPO_INLINE_CONSTEXPR) + // Named C++20 modules require CPOs (customization point objects) to have // external linkage. The anonymous-namespace singleton idiom used by asio's // prefer/require/query/require_concept CPOs creates TU-local entities that diff --git a/include/asio/detail/socket_types.hpp b/include/asio/detail/socket_types.hpp index 97585fad11..55c59ec580 100644 --- a/include/asio/detail/socket_types.hpp +++ b/include/asio/detail/socket_types.hpp @@ -99,8 +99,8 @@ ASIO_INLINE_NAMESPACE_BEGIN namespace detail { #if defined(ASIO_WINDOWS_RUNTIME) -const int max_addr_v4_str_len = 256; -const int max_addr_v6_str_len = 256; +ASIO_DETAIL_CONSTEXPR_VAR int max_addr_v4_str_len = 256; +ASIO_DETAIL_CONSTEXPR_VAR int max_addr_v6_str_len = 256; typedef unsigned __int32 u_long_type; typedef unsigned __int16 u_short_type; struct in4_addr_type { u_long_type s_addr; }; @@ -185,10 +185,10 @@ typedef int signed_size_type; # define ASIO_OS_DEF_SA_NOCLDWAIT 0x4 #elif defined(ASIO_WINDOWS) || defined(ASIO_CYGWIN_W32_SOCKETS) typedef SOCKET socket_type; -const SOCKET invalid_socket = INVALID_SOCKET; -const int socket_error_retval = SOCKET_ERROR; -const int max_addr_v4_str_len = 256; -const int max_addr_v6_str_len = 256; +ASIO_DETAIL_CONSTEXPR_VAR SOCKET invalid_socket = INVALID_SOCKET; +ASIO_DETAIL_CONSTEXPR_VAR int socket_error_retval = SOCKET_ERROR; +ASIO_DETAIL_CONSTEXPR_VAR int max_addr_v4_str_len = 256; +ASIO_DETAIL_CONSTEXPR_VAR int max_addr_v6_str_len = 256; typedef sockaddr socket_addr_type; typedef in_addr in4_addr_type; typedef ip_mreq in4_mreq_type; @@ -291,22 +291,22 @@ struct sockaddr_un_type { u_short sun_family; char sun_path[108]; }; # define ASIO_OS_DEF_AI_ADDRCONFIG 0 # endif # if defined (_WIN32_WINNT) -const int max_iov_len = 64; +ASIO_DETAIL_CONSTEXPR_VAR int max_iov_len = 64; # else -const int max_iov_len = 16; +ASIO_DETAIL_CONSTEXPR_VAR int max_iov_len = 16; # endif # define ASIO_OS_DEF_SA_RESTART 0x1 # define ASIO_OS_DEF_SA_NOCLDSTOP 0x2 # define ASIO_OS_DEF_SA_NOCLDWAIT 0x4 #else typedef int socket_type; -const int invalid_socket = -1; -const int socket_error_retval = -1; -const int max_addr_v4_str_len = INET_ADDRSTRLEN; +ASIO_DETAIL_CONSTEXPR_VAR int invalid_socket = -1; +ASIO_DETAIL_CONSTEXPR_VAR int socket_error_retval = -1; +ASIO_DETAIL_CONSTEXPR_VAR int max_addr_v4_str_len = INET_ADDRSTRLEN; #if defined(INET6_ADDRSTRLEN) -const int max_addr_v6_str_len = INET6_ADDRSTRLEN + 1 + IF_NAMESIZE; +ASIO_DETAIL_CONSTEXPR_VAR int max_addr_v6_str_len = INET6_ADDRSTRLEN + 1 + IF_NAMESIZE; #else // defined(INET6_ADDRSTRLEN) -const int max_addr_v6_str_len = 256; +ASIO_DETAIL_CONSTEXPR_VAR int max_addr_v6_str_len = 256; #endif // defined(INET6_ADDRSTRLEN) typedef sockaddr socket_addr_type; typedef in_addr in4_addr_type; @@ -412,10 +412,10 @@ typedef int signed_size_type; # define ASIO_OS_DEF_AI_ADDRCONFIG 0 # endif # if defined(IOV_MAX) -const int max_iov_len = IOV_MAX; +ASIO_DETAIL_CONSTEXPR_VAR int max_iov_len = IOV_MAX; # else // POSIX platforms are not required to define IOV_MAX. -const int max_iov_len = 16; +ASIO_DETAIL_CONSTEXPR_VAR int max_iov_len = 16; # endif # define ASIO_OS_DEF_SA_RESTART SA_RESTART # define ASIO_OS_DEF_SA_NOCLDSTOP SA_NOCLDSTOP @@ -425,9 +425,9 @@ const int max_iov_len = 16; # define ASIO_OS_DEF_SA_NOCLDWAIT 0 # endif // defined(SA_NOCLDWAIT) #endif -const int custom_socket_option_level = 0xA5100000; -const int enable_connection_aborted_option = 1; -const int always_fail_option = 2; +ASIO_DETAIL_CONSTEXPR_VAR int custom_socket_option_level = 0xA5100000; +ASIO_DETAIL_CONSTEXPR_VAR int enable_connection_aborted_option = 1; +ASIO_DETAIL_CONSTEXPR_VAR int always_fail_option = 2; } // namespace detail ASIO_INLINE_NAMESPACE_END From 546c3c2576bbd5b85d499fea23af23f12b0c886f Mon Sep 17 00:00:00 2001 From: kydos Date: Fri, 29 May 2026 08:41:01 +0200 Subject: [PATCH 3/3] Fix macro ordering: ASIO_DETAIL_CONSTEXPR_VAR must be defined after ASIO_CPO_INLINE_CONSTEXPR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ASIO_DETAIL_CONSTEXPR_VAR macro selects between 'inline constexpr' and 'const' via a #if on ASIO_CPO_INLINE_CONSTEXPR. The C preprocessor evaluates that #if when the macro is defined, not when it is expanded, so ASIO_DETAIL_CONSTEXPR_VAR must be defined after the ASIO_GCC_NAMED_MODULES → ASIO_CPO_INLINE_CONSTEXPR detection block in the same file. --- include/asio/detail/config.hpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/include/asio/detail/config.hpp b/include/asio/detail/config.hpp index 84270ff943..cb960b02df 100644 --- a/include/asio/detail/config.hpp +++ b/include/asio/detail/config.hpp @@ -1658,18 +1658,6 @@ # define ASIO_VERSIONED_NAME(name) asio_ ## name #endif // defined(ASIO_VERSION_NAMESPACE) -// Namespace-scope `const` variables (non-inline) have internal linkage in -// C++. When included in a named module's global module fragment they become -// TU-local entities; GCC 15+ rejects module functions that reference them. -// ASIO_DETAIL_CONSTEXPR_VAR expands to `inline constexpr` under -// ASIO_CPO_INLINE_CONSTEXPR, giving these variables external linkage and -// making them safe to reference from named modules. -#if defined(ASIO_CPO_INLINE_CONSTEXPR) -# define ASIO_DETAIL_CONSTEXPR_VAR inline constexpr -#else -# define ASIO_DETAIL_CONSTEXPR_VAR const -#endif // defined(ASIO_CPO_INLINE_CONSTEXPR) - // Named C++20 modules require CPOs (customization point objects) to have // external linkage. The anonymous-namespace singleton idiom used by asio's // prefer/require/query/require_concept CPOs creates TU-local entities that @@ -1690,4 +1678,18 @@ # endif // defined(ASIO_GCC_NAMED_MODULES) #endif // !defined(ASIO_CPO_INLINE_CONSTEXPR) +// Namespace-scope `const` variables (non-inline) have internal linkage in +// C++. When included in a named module's global module fragment they become +// TU-local entities; GCC 15+ rejects module functions that reference them. +// ASIO_DETAIL_CONSTEXPR_VAR expands to `inline constexpr` under +// ASIO_CPO_INLINE_CONSTEXPR, giving these variables external linkage and +// making them safe to reference from named modules. +// NOTE: this block must come AFTER the ASIO_CPO_INLINE_CONSTEXPR detection +// above so that the #if test is evaluated with the correct macro state. +#if defined(ASIO_CPO_INLINE_CONSTEXPR) +# define ASIO_DETAIL_CONSTEXPR_VAR inline constexpr +#else +# define ASIO_DETAIL_CONSTEXPR_VAR const +#endif // defined(ASIO_CPO_INLINE_CONSTEXPR) + #endif // ASIO_DETAIL_CONFIG_HPP