Skip to content

Optionaly add support for using the standard library module rather than standard library header files #102

Description

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.

  1. Decorate names with std:: if the code currently assumes they are in the global namespace
  2. Add import std; and macro guard out the #include of standard library headers
  3. Make the use of GSL optional
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions