I have a lot of polymorphic classes. Each of them has a nested class named Configuration
containing all the hyperparameters required by the class. For each class tree, I create a variant of the possible configurations and a function instantiate
able to create the instance from the configuration. Like in the example below.
# include <variant>
# include <memory>
class Base
{};
class A : public Base
{
public:
struct Configuration { /*hyperparameters*/ };
A(Configuration const&) {}
};
class B : public Base
{
public:
struct Configuration { /*hyperparameters*/ };
B(Configuration const&) {}
};
struct BaseConfiguration
{
using type = std::variant<A::Configuration, B::Configuration>;
static std::unique_ptr<Base> instantiate(type const& config);
};
struct BaseVisitor
{
// My problem is here...
std::unique_ptr<Base> operator()(A::Configuration const& config)
{ return std::make_unique<A>(config); }
std::unique_ptr<Base> operator()(B::Configuration const& config)
{ return std::make_unique<B>(config); }
};
std::unique_ptr<Base> BaseConfiguration::instantiate(type const& config)
{
return std::visit(BaseVisitor(), config);
}
int main()
{
BaseConfiguration::type config = A::Configuration();
std::unique_ptr<Base> x = BaseConfiguration::instantiate(config);
return 0;
}
I would like to avoid the redudant code in the BaseVisitor
class. All the functions are exaclty the same (a lot of functions for some hierarcies). Is there a way to reduce this code ? I tried with the following template approach, but it's doesn't compile (with C++17).
struct BaseVisitor
{
template <typename X>
std::unique_ptr<Base> operator()(typename X::Configuration const& config)
{ return std::make_unique<X>(config); }
};
Compiler error:
<source>: In static member function 'static std::unique_ptr<Base> BaseConfiguration::instantiate(const type&)':
<source>:36:22: error: no matching function for call to 'visit(BaseVisitor, const BaseConfiguration::type&)'
36 | return std::visit(BaseVisitor(), config);
| ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
The whole trick is to deliver expected type to visitor. Here is an one of possible solutions:
class Base
{
public:
virtual ~Base() = default;
};
class A : public Base
{
public:
struct Configuration {
using configured_type = A;
/*hyperparameters*/
};
A(Configuration const&) {}
};
class B : public Base
{
public:
struct Configuration {
using configured_type = B;
/*hyperparameters*/
};
B(Configuration const&) {}
};
struct BaseConfiguration
{
using type = std::variant<A::Configuration, B::Configuration>;
static std::unique_ptr<Base> instantiate(type const& config);
};
std::unique_ptr<Base> BaseConfiguration::instantiate(type const& config)
{
return std::visit([] (const auto& detail_config) -> std::unique_ptr<Base> {
using configured_type = typename std::decay_t<decltype(detail_config)>::configured_type;
return std::make_unique<configured_type>(detail_config);
}, config);
}