I’ve found myself recently needing to handle passing and managing options across all of the components of our application.
I wanted all components to have their own options that could be managed externally.
Approach
I nest the options struct in the class and let it be qualified by the class name.
class MyComponent {
public:
struct Options {
struct {
bool some_option{false};
/* some other options */
} MyComponent;
};
MyComponent() {}
MyComponent(std::shared_ptr<Options>& options) :
m_options{options} {}
/* ... */
private:
std::shared_ptr<Options> m_options{new Options};
};
We will devise a way to aggregate options and get a specific slice of options from it.
template <typename... TOptions>
struct OptionsAggregate : public TOptions... {
using Type = OptionsAggregate<TOptions...>;
template <typename TOptionType>
auto Get() -> TOptionType& {
static_assert(std::is_base_of_v<TOptionType, Type>,
"Requested options are not a base of this aggregate");
return static_cast<TOptionType&>(*this);
}
};
So this variadic class template will take several options classes and inherit from them. It also has a Get
method to get a slice of the aggregate.
We will have a ComponentManager
that manages all of the components and their options.
template <typename TOptions>
class ComponentManager {
public:
ComponentManager() {}
ComponentManager(std::shared_ptr<MyComponent> component,
std::shared_ptr<Options> options) :
m_options{std::move(options)} {}
void Run();
private:
std::shared_ptr<TOptions> m_options{new TOptions};
};
We can then use all of these things like the following:
struct GeneralOptions {
bool debug{false};
};
using ComponentOptions = OptionsAggregate<GeneralOptions, MyComponent::Options>;
auto options = std::make_shared<ComponentOptions>();
options->Get<GeneralOptions>().debug = true;
/* set other options */
// the options that component can see are only a slice of the overall options
auto component = std::make_shared<MyComponent>(options);
ComponentManager manager{component, options};
A problem I discovered with this method appears when using the slicing syntax from ComponentManager
methods which results in very unfortunate syntax. Imagine component manager had a method called Run
:
template<typename TOptions>
void ComponentManager<TOptions>::Run() {
auto& general_options = m_options->template Get<GeneralOptions>();
/* use general_options */
}
This occurs because m_options
depends on a template parameter as well as OptionsAggregate::Get
.
Check Compiler Explorer for a full example.