I've just started a new project and want to use modules. To keep things simple for this question, I have 2 files: Engine and Plugin.
Plugin:
export module Plugin;
export namespace Lib {
template<typename T>
concept Plugin = requires(T t)
{
{ t.Init() } -> std::same_as<void>;
{ t.Run() } -> std::same_as<void>;
};
struct IPlugin
{
virtual ~IPlugin() = default;
virtual void Init() = 0;
virtual void Run() = 0;
};
template <Plugin T>
struct PluginWrapper final : IPlugin
{
explicit PluginWrapper(T p)
: plugin(std::move(p))
{
}
void Init() override { plugin.Init(); }
void Run() override { plugin.Run(); }
T plugin;
};
}
Engine:
export module Engine;
import Plugin;
export namespace Lib {
struct Engine
{
template<Plugin P>
void AddPlugin(P plugin)
{
m_plugins.push_back(std::make_unique<PluginWrapper<P>>(std::move(plugin)));
}
private:
std::vector<std::unique_ptr<IPlugin>> m_plugins;
};
}
And I want only the Plugin concept to be exposed to the user of this library, as if the other things don't exist.
So the only way to get this done is to use partitions, but I am not sure if they were made for such things.
Module partitions serve purely organizational purposes within one module. You should decide what you want your modules' interfaces to look like before you think about how to split those modules into partitions/files. You can not, in general, organize your definitions into files first and set up the modules to match, which is what you seem to be doing. In this case, that means you should first answer the question
Do I want two modules,
EngineandPlugin, or one module,Engine?
As you've noticed, modules have no hierarchy: they have a single public interface that is the same for all users. If Engine and Plugin are two separate modules, then anything in Plugin that is needed by Engine will also be visible to users. Your options are then
Engine (which exports only struct Lib::Engine and concept Lib::Plugin).Engine (which exports only struct Lib::Engine) and Plugin (which exports only concept Lib::Plugin) and move the implementation classes IPlugin and PluginWrapper into Engine.If you go with option 1, you can mostly keep the 2-file structure you have now. You'll need to a) make Plugin a module partition of Engine and b) change what is exported or not. You could also not use partitions and put everything into one big Engine file. Partitions are not meant for making things visible or invisible—they are for organization.
If you go with option 2, I think it would make sense to put the Plugin implementation stuff into a partition of Engine. So you would have three files:
Plugin:
export module Plugin;
import std;
export namespace Lib {
template<typename T>
concept Plugin = requires(T t) {
{ t.Init() } -> std::same_as<void>;
{ t.Run() } -> std::same_as<void>;
};
}
Engine:
export module Engine;
import std;
import :Plugin;
export namespace Lib {
struct Engine {
template<Plugin P>
void AddPlugin(P plugin) {
m_plugins.push_back(std::make_unique<PluginWrapper<P>>(std::move(plugin)));
}
private:
std::vector<std::unique_ptr<IPlugin>> m_plugins;
};
}
Engine_Plugin:
module Engine:Plugin;
import std;
import Plugin;
namespace Lib {
struct IPlugin {
virtual ~IPlugin() = default;
virtual void Init() = 0;
virtual void Run() = 0;
};
template<Plugin T>
struct PluginWrapper final : IPlugin {
explicit PluginWrapper(T p) : plugin(std::move(p)) { }
void Init() override { plugin.Init(); }
void Run() override { plugin.Run(); }
T plugin;
};
}
(Or, as in option 1, you could just put IPlugin and PluginWrapper into the Engine file and not use partitions.) To reiterate, if you want two separate modules, you can not organize concept Plugin, struct IPlugin, and struct PluginWrapper into one file and struct Engine into another file. struct IPlugin and struct PluginWrapper have to be in the same module as struct Engine if you want them to be used by struct Engine but not anyone else.