Guidelines to support Boost components and their std alternatives

Dear all, Some libraries have APIs that rely on type from Boost libraries that have std alternative in C++17 (think filesystem, variant, optional). I wonder if there are some guidelines available for library maintainers to support both alternatives (or concerns if it is not a good idea)? For example, in Boost.MPI, the foreseen solution is to introduce an alias for optional in the boost::mpi namespace that will conditionally refer to std::optional or boost::optional depending on BOOST_NO_CXX17_HDR_OPTIONAL and a new BOOST_MPI_FORCE_BOOST_OPTIONAL macro. See https://github.com/boostorg/mpi/issues/168 Thank you, Samuel

El 04/09/2025 a las 9:54, Samuel Debionne via Boost escribió:
Dear all,
Some libraries have APIs that rely on type from Boost libraries that have std alternative in C++17 (think filesystem, variant, optional). I wonder if there are some guidelines available for library maintainers to support both alternatives (or concerns if it is not a good idea)?
For example, in Boost.MPI, the foreseen solution is to introduce an alias for optional in the boost::mpi namespace that will conditionally refer to std::optional or boost::optional depending on BOOST_NO_CXX17_HDR_OPTIONAL and a new BOOST_MPI_FORCE_BOOST_OPTIONAL macro. See https://github.com/boostorg/mpi/issues/168
Thank you, Samuel
I don't think we have an agreed guideline. In some corner cases, specially if the library is template-based I try to support both alternatives. E.g. Interprocess supports both std::chrono and boost::chrono in the synchronization primitives. For your use case, if it's an internal dependency (not exported to the API), you could just choose one variant. When the API is involved, the situation is a bit tricky and a compilation flag could do the job, but things can be fragile and error prone, specially if a user can choose between std or boost when both alternatives are available. Depending on Boost libraries also increases coupling and the general trend in recent Boost versions is to reduce dependencies as much as possible. Best, Ion

Am 04.09.25 um 10:48 schrieb Ion Gaztañaga via Boost:
El 04/09/2025 a las 9:54, Samuel Debionne via Boost escribió:
For example, in Boost.MPI, the foreseen solution is to introduce an alias for optional in the boost::mpi namespace that will conditionally refer to std::optional or boost::optional depending on BOOST_NO_CXX17_HDR_OPTIONAL and a new BOOST_MPI_FORCE_BOOST_OPTIONAL macro. See https://github.com/boostorg/mpi/issues/168 I'm wary of using a macro to change things unless you can completely rule out ODR violations. Boost is used widely enough that you can end up linking a library not defining the macro together with one that does. So be sure that it doesn't affect your usage. I think providing a function taking either type as an argument is fine. But using either type inside a function with the same signature is not. In some corner cases, specially if the library is template-based I try to support both alternatives. E.g. Interprocess supports both std::chrono and boost::chrono in the synchronization primitives. Another way I support at one point is a template function supporting a foo-like. I.e. I have an SFINAE that allows passing something that is "filesystem::path-enough". This way you get Boost and std types in a single function. Generally I'd encourage accepting either variant. Returning one is harder of course. For your use case, if it's an internal dependency (not exported to the API), you could just choose one variant. When the API is involved, the situation is a bit tricky and a compilation flag could do the job, but things can be fragile and error prone, specially if a user can choose between std or boost when both alternatives are available. See above: If it is internal you can run into ODR issues.

Samuel Debionne wrote:
Some libraries have APIs that rely on type from Boost libraries that have std alternative in C++17 (think filesystem, variant, optional). I wonder if there are some guidelines available for library maintainers to support both alternatives (or concerns if it is not a good idea)?
I don't know about any official policy on the matter, but I and others have previously gotten burned by having it be a compile-time check at the point of inclusion. I feel strongly that the proper solution is to rely on Boost Config, so that everything compiling against a build of Boost will compile in the same way and thereby avoid any ODR violations. Matt

чт, 4 сент. 2025 г. в 20:16, Matt Gruenke via Boost <boost@lists.boost.org>:
I don't know about any official policy on the matter, but I and others have previously gotten burned by having it be a compile-time check at the point of inclusion. I feel strongly that the proper solution is to rely on Boost Config, so that everything compiling against a build of Boost will compile in the same way and thereby avoid any ODR violations.
This doesn't solve the issue, it just pushes the problem to link time. E.g. you build Boost in a configuration that doesn't have std::optional, then you link to it in a configuration that does. This is actually trivially done by accident (e.g. you build Boost with a compiler that defaults to C++14, then you use it with C++17 enabled). For header-only libraries the problem also exists, it's just pushed into users' libraries. The least surprising way to do this is to ask users to explicitly enable standard equivalents. This still has the same problem fundamentally (nothing prevents users from defining the macro when building Boost and not defining it when using it, or vice versa). But because it requires actions from users it should significantly decrease the chances of it happening, and if it does happen, the reason should be more obvious to them. Note that because these problems occur nevertheless, Boost.JSON has removed the option to switch to std::error_code and std::pmr.

Дмитрий Архипов wrote:
чт, 4 сент. 2025 г. в 20:16, Matt Gruenke via Boost <boost@lists.boost.org>:
I don't know about any official policy on the matter, but I and others have previously gotten burned by having it be a compile-time check at the point of inclusion. I feel strongly that the proper solution is to rely on Boost Config, so that everything compiling against a build of Boost will compile in the same way and thereby avoid any ODR violations.
This doesn't solve the issue, it just pushes the problem to link time. E.g. you build Boost in a configuration that doesn't have std::optional, then you link to it in a configuration that does. This is actually trivially done by accident (e.g. you build Boost with a compiler that defaults to C++14, then you use it with C++17 enabled). For header-only libraries the problem also exists, it's just pushed into users' libraries.
The least surprising way to do this is to ask users to explicitly enable standard equivalents. This still has the same problem fundamentally (nothing prevents users from defining the macro when building Boost and not defining it when using it, or vice versa). But because it requires actions from users it should significantly decrease the chances of it happening, and if it does happen, the reason should be more obvious to them.
Note that because these problems occur nevertheless, Boost.JSON has removed the option to switch to std::error_code and std::pmr.
Basically, the only reliable solution is to just require C++17 in the library and use the standard components unconditionally (or not require it and use the Boost components unconditionally.) In some cases, e.g. when using boost::system::error_code or boost::core::string_view, this still allows use with std::error_code or std::string_view because the Boost components interoperate with the standard ones. I think that most libraries can afford to require C++17 today and avoid the hassle. C++17 is default on GCC 11 and Clang 13. It's too bad that the sweet spot for the language is C++14 and for the library C++17, but, well.

Дмитрий Архипов Wrote:
чт, 4 сент. 2025 г. в 20:16, Matt Gruenke via Boost <boost@lists.boost.org>:
I don't know about any official policy on the matter, but I and others have previously gotten burned by having it be a compile-time check at the point of inclusion. I feel strongly that the proper solution is to rely on Boost Config, so that everything compiling against a build of Boost will compile in the same way and thereby avoid any ODR violations.
This doesn't solve the issue, it just pushes the problem to link time.
I think it's reasonable to expect people to compile, link, and run against the same build of Boost.
E.g. you build Boost in a configuration that doesn't have std::optional, then you link to it in a configuration that does. This is actually trivially done by accident (e.g. you build Boost with a compiler that defaults to C++14, then you use it with C++17 enabled).
This illustrates perfectly why it should not be a compile-time check, at the point of inclusion. The Boost Config headers are generated when boost, itself, is compiled. When a precompiled build of boost is installed, the config headers come with it.
The least surprising way to do this is to ask users to explicitly enable standard equivalents.
This fundamentally doesn't work, when the header you're including is also included and used by code in one or more of the non- header-only boost libraries you're linking against. That's how you get ODR violations. The definition of types must be consistent by all code which includes them and might be linked into the same executable or library. That's why Boost.Config is the correct solution to this problem, to the extent it's not simply a hard-wired requirement of the library. Peter Dimov avoids the problem by essentially doing away with any choice. That also works, so long as everyone can agree on the minimum version requirement. Matt

Am 10.09.25 um 20:34 schrieb Matt Gruenke via Boost:
Дмитрий Архипов Wrote:
чт, 4 сент. 2025 г. в 20:16, Matt Gruenke via Boost<boost@lists.boost.org>:
I don't know about any official policy on the matter, but I and others have previously gotten burned by having it be a compile-time check at the point of inclusion. I feel strongly that the proper solution is to rely on Boost Config, so that everything compiling against a build of Boost will compile in the same way and thereby avoid any ODR violations. This doesn't solve the issue, it just pushes the problem to link time. I think it's reasonable to expect people to compile, link, and run against the same build of Boost. You could still have an independently compiled Boost with C++11, a dependency using Boost compiled with C++14 and your own library using C++17 causing potentially 3 different "contents" of the Boost headers. I'm fine with a link-time error as the alternative would be an ODR-violation so detecting it at compile/linktime is better. E.g. you build Boost in a configuration that doesn't have std::optional, then you link to it in a configuration that does. This is actually trivially done by accident (e.g. you build Boost with a compiler that defaults to C++14, then you use it with C++17 enabled). This illustrates perfectly why it should not be a compile-time check, at the point of inclusion. The Boost Config headers are generated when boost, itself, is compiled. When a precompiled build of boost is installed, the config headers come with it. The config headers are not generated (yet). They are kept as-is during installation and just contain a set of macros to enable disable stuff based on current compiler and flags. Or are you proposing that Boost config headers *should* be generated? The least surprising way to do this is to ask users to explicitly enable standard equivalents. This fundamentally doesn't work, when the header you're including is also included and used by code in one or more of the non- header-only boost libraries you're linking against. That's how you get ODR violations. Or by code of dependencies. Even header-only dependencies could cause issues when you define BOOST_X=1 but one of the dependencies headers defines BOOST_X=2 and you might have different TU where one does include that dependency header and one that does not. The definition of types must be consistent by all code which includes them and might be linked into the same executable or library. That's why Boost.Config is the correct solution to this problem, to the extent it's not simply a hard-wired requirement of the library. What is meant by the last sentence? Peter Dimov avoids the problem by essentially doing away with any choice. That also works, so long as everyone can agree on the minimum version requirement. "minimum version" of what? E.g. you'd actually need to agree on a specific C++ version and use that for the whole compilation including Boost and potentially other dependencies using Boost.
participants (6)
-
Alexander Grund
-
Ion Gaztañaga
-
Matt Gruenke
-
Peter Dimov
-
Samuel Debionne
-
Дмитрий Архипов