Currently this project uses #include for the standard library. This issue proposes allowing optionally using import std; instead. By default the library would still use standard library headers you would have to opt into import std; These change would allow a consumer to use import std; in their own code bases without having to resort to tricks and works arounds when they #include IFC headers
This requires a number changes to both the code and the CMakeLists.txt files in the library. This proposal is based on migrating other libraries and internal code. The changes are split up into the following.
- Decorate names with
std:: if the code currently assumes they are in the global namespace
- Add
import std; and macro guard out the #include of standard library headers
- Make the use of GSL optional
- CMake changes
Each should be able to be done in mostly if not totally non-overlapping PR's. This should make the PR's much easier to review and can be applied in any order. I will create a PR for at least item 1 so people can take a look. Note that it should be possible to aplly any of them in any order without breaking the existing build.
Decorate names with std:: if the code currently assumes they are in the global namespace
In the current code there are a number of cases where integral types like uint8_t are assume to be in the global namespace
If you import std; these correctly only appear in the namespace std.
The proposal is to simply addstd:: to them.
An alternative would be import std.compat; or add a bunch of using std::uint8_t;. I think adding std:: is more obvious, module std.compat to me is a intermediate step to module std so why not just go straight to module std
Add import std; and macro guard out the #include of standard library header
The proposal is to macro guard the old #include lines. If IFC_BUILD_USING_STD_MODULE is not defined you get the existing behavior.
#if defined(IFC_BUILD_USING_STD_MODULE)
import std;
#else
#include <vector>
#endif
So why not just do
import std;
#include <vector>
Well I believe that doesn't compile on any compiler as of today and have been told it is very difficult to do for "reasons". So how about
#include <vector>
import std;
Well that does compile but it throws away the main reason for modules, which is interface encapsulation. So on MSVC the following will compile
#include <vector>
import std;
std::_Iterator_base x; // Magic internal symbol that leaks
So all the internal symbols and macros will still pollute any user. You are also paying the compile costs of both the #include and the import std;. This may not be so bad if users can use IFC as a module itself as it would be able to control it's own visible interface. In general from experience in a more complex code base using multiple third party libraries it is extremely difficult to avoid arbitrary ordering of #include and import std;
Note there are a few cases where you do need to include a header, so
#include <version> // For feature test macros
#include <stddef.h> // For offsetof
import std;
Generally this is most to access things only available as macros or C functions not in the standard library. For more info see https://wg21.link/p2654
Make the use of GSL optional
I suspect this might be more controversial.
The GSL library does not currently allow the use of import std;. This makes it very hard to include GSL headers without triggering compiler errors and of course polluting the global namespace.
I will create some issues/PRs to fix GSL but in the mean time I reviewed the use of GSL in this library.
It seems like all uses of the GSL are span with the exception of one use of gsl::finally. It is not clear why gsl::span is preferred over std::span. Maybe it is to support older compilers or possibly it is bounds checking. The former does not seem like a reason to penalize more up to date users. The latter may fixed by standard library hardening modes, see wg21.link/P3471 and https://github.com/microsoft/STL/wiki/STL-Hardening/0d241a364899a6b25a16a17dbb9d67606f8a8f72
A macro guard and a using can be used as follows to allow use of either span
#if !defined(IFC_BUILD_WITHOUT_GSL)
#include <gsl/span>
#endif
namespace ifc {
#if defined(IFC_BUILD_WITHOUT_GSL)
template<typename T> using span = std::span<T>;
#else
template<typename T> using span = gsl::span<T>;
#endif
...
ifc::span<const std::byte> x;
...
}
The using statement is done in the ifc namespace to avoid polluting the global namespace for consumers. The call sites can explicitly use ifc::span to avoid any ambiguity.
Not sure what to do about the one use of gsl::finally, maybe have a local copy if it's a no GSL build? Any suggestions?
Cmake changes
This could be done in many ways. One would be to have options like
option(IFC_BUILD_USING_STD_MODULE "Build using the standard library module" OFF)
option(IFC_BUILD_WITHOUT_GSL "Build using the GSL span class" ON)
which can then be used as follows
if(IFC_BUILD_USING_STD_MODULE)
add_compile_definitions(IFC_BUILD_USING_STD_MODULE)
set(IFC_BUILD_WITHOUT_GSL ON)
endif()
if(IFC_BUILD_WITHOUT_GSL)
add_compile_definitions(IFC_BUILD_WITHOUT_GSL)
else()
find_package(Microsoft.GSL REQUIRED)
endif()
The guard macro and options are written this way to ensure that things just carry on working exactly as before. You have to use the options to opt into changes.
vcpkg
I am not very knowledgeable about packaging libraries for vcpkg, to say the least.
As a user you would really want some way to indicate that you want a import std; build of the library and that things like IFC_BUILD_USING_STD_MODULE would be set for you when you add a target_link_libraries dependency in your CMakeLists.txt. It was suggested to me that you might be able to use vcpkg "features" for this but I have not done any investigation. See https://learn.microsoft.com/en-us/vcpkg/concepts/features. I have been trying to get Microsoft to write a blog article on how they suggest vcpkg should work for modules for both the producer and consumer.
Obviously at a minimum existing vcpkg consumers of IFC should just carry on working without change.
Build times
For those who think this is all about build times, it's not it's about controlling the interface surface of a library, the build time on my reasonable desktop for both the #include and import std; version were very similar at about 12 seconds. I did try loading and printing a number of .ifc files including the std.ixx.ifc file.
Currently this project uses
#includefor the standard library. This issue proposes allowing optionally usingimport std;instead. By default the library would still use standard library headers you would have to opt intoimport std;These change would allow a consumer to useimport std;in their own code bases without having to resort to tricks and works arounds when they#includeIFC headersThis requires a number changes to both the code and the
CMakeLists.txtfiles in the library. This proposal is based on migrating other libraries and internal code. The changes are split up into the following.std::if the code currently assumes they are in the global namespaceimport std;and macro guard out the#includeof standard library headersEach should be able to be done in mostly if not totally non-overlapping PR's. This should make the PR's much easier to review and can be applied in any order. I will create a PR for at least item 1 so people can take a look. Note that it should be possible to aplly any of them in any order without breaking the existing build.
Decorate names with
std::if the code currently assumes they are in the global namespaceIn the current code there are a number of cases where integral types like
uint8_tare assume to be in the global namespaceIf you
import std;these correctly only appear in the namespace std.The proposal is to simply add
std::to them.An alternative would be
import std.compat;or add a bunch ofusing std::uint8_t;. I think addingstd::is more obvious, modulestd.compatto me is a intermediate step to modulestdso why not just go straight to modulestdAdd
import std;and macro guard out the#includeof standard library headerThe proposal is to macro guard the old
#includelines. IfIFC_BUILD_USING_STD_MODULEis not defined you get the existing behavior.So why not just do
Well I believe that doesn't compile on any compiler as of today and have been told it is very difficult to do for "reasons". So how about
Well that does compile but it throws away the main reason for modules, which is interface encapsulation. So on MSVC the following will compile
So all the internal symbols and macros will still pollute any user. You are also paying the compile costs of both the
#includeand theimport std;. This may not be so bad if users can use IFC as a module itself as it would be able to control it's own visible interface. In general from experience in a more complex code base using multiple third party libraries it is extremely difficult to avoid arbitrary ordering of#includeandimport std;Note there are a few cases where you do need to include a header, so
Generally this is most to access things only available as macros or C functions not in the standard library. For more info see https://wg21.link/p2654
Make the use of GSL optional
I suspect this might be more controversial.
The GSL library does not currently allow the use of
import std;. This makes it very hard to include GSL headers without triggering compiler errors and of course polluting the global namespace.I will create some issues/PRs to fix GSL but in the mean time I reviewed the use of GSL in this library.
It seems like all uses of the GSL are
spanwith the exception of one use ofgsl::finally. It is not clear whygsl::spanis preferred overstd::span. Maybe it is to support older compilers or possibly it is bounds checking. The former does not seem like a reason to penalize more up to date users. The latter may fixed by standard library hardening modes, see wg21.link/P3471 and https://github.com/microsoft/STL/wiki/STL-Hardening/0d241a364899a6b25a16a17dbb9d67606f8a8f72A macro guard and a using can be used as follows to allow use of either
spanThe using statement is done in the
ifcnamespace to avoid polluting the global namespace for consumers. The call sites can explicitly useifc::spanto avoid any ambiguity.Not sure what to do about the one use of gsl::finally, maybe have a local copy if it's a no GSL build? Any suggestions?
Cmake changes
This could be done in many ways. One would be to have options like
which can then be used as follows
The guard macro and options are written this way to ensure that things just carry on working exactly as before. You have to use the options to opt into changes.
vcpkg
I am not very knowledgeable about packaging libraries for vcpkg, to say the least.
As a user you would really want some way to indicate that you want a
import std;build of the library and that things likeIFC_BUILD_USING_STD_MODULEwould be set for you when you add atarget_link_librariesdependency in yourCMakeLists.txt. It was suggested to me that you might be able to use vcpkg "features" for this but I have not done any investigation. See https://learn.microsoft.com/en-us/vcpkg/concepts/features. I have been trying to get Microsoft to write a blog article on how they suggest vcpkg should work for modules for both the producer and consumer.Obviously at a minimum existing vcpkg consumers of IFC should just carry on working without change.
Build times
For those who think this is all about build times, it's not it's about controlling the interface surface of a library, the build time on my reasonable desktop for both the
#includeandimport std;version were very similar at about 12 seconds. I did try loading and printing a number of.ifcfiles including thestd.ixx.ifcfile.