Header only library as a module?

823 Views Asked by At

I'm authoring a templated header only library. It has no state, no global variables, no .cpp that needs to be compiled.

Is it possible to export/consume this as a module? How? What are the benefits? What are the pitfalls?

There are some convenience macros that I probably want the user to have. What about those?

I have found an example using #ifdef ... to cater for both module and old-school cases. I think I want to avoid that.

1

There are 1 best solutions below

0
On

I have found an example using #ifdef ... to cater for both module and old-school cases. I think I want to avoid that.

Broadly speaking, that is not the best way to do this. You can do it by just importing the header as a header unit with import <header_file.hpp>; but you'll be losing some important aspects of modules. You're going to need to do a limited amount of #defineing if you want your module version to work well.

Just about every header-only library will have some declarations that are considered part of the library, but it will also have some which are not. These are usually put into a detail namespace or other attempts are made to hide them from users.

Modules have a mechanism for doing that: export. Or more to the point, they have a way to not put something in your interface: don't export it. But this requires explicitly tagging your interfaces with the export keyword... which won't work in non-module builds. So you need a macro to say if the build is a module build or not, so that you can have a #define that resolves to export or to nothing.

You could do an export namespace my_namespace {}; to try to export everything, but that can have... unpleasant side effects.

You also may need to explicitly inline certain class members of exported classes, as modules do not implicitly inline defined members of non-template classes the way a non-module build does. Fortunately, adding inline will work fine on non-module builds.

Writing a header-only library and a module interface file for it, such that a user can include whichever they prefer, isn't too difficult. But it does require some care, and to best take advantage of module features, you should use some macros.

The primary module interface unit would look like this:

module; //Begin global module fragment.

<external header includes>

export module MyModuleName; //Begin the actual module purview

#define MY_MODULE_NAME_EXPORT export;

#include "my_header1.hpp"
#include "my_header2.hpp"

The <external header includes> is very important. You need to #include every file that your library explicitly includes. If you include parts of the C++ standard library, then include them here.

The point of this is to stop those headers from shoving the contents of those headers into the module's purview. You want your inclusion of your library to be the only code that gets shoved into the module. So you include those headers in the global module fragment, and use their include-guards to prevent later inclusion.

And yes, this does mean you have to keep two separate lists of these headers. You can write a tool to look through all of your headers and build that list for you, but one way or another it's still a thing you need to have.

At the top of your library headers, after any include guards, you need to have this:

#ifndef MY_MODULE_NAME_EXPORT
#define MY_MODULE_NAME_EXPORT
#endif

This allows you to decorate anything you want to export with MY_MODULE_NAME_EXPORT:

MY_MODULE_NAME_EXPORT void some_function()
{
  ...
}

If you're in a module build, that will export the function. If you aren't, then it won't.