c++c++17variantvisitor-pattern

Visit variant of nested classes : how to avoid redundant code?


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 same code in Compiler Explorer


Solution

  • 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);
    }
    

    https://godbolt.org/z/f37WhM898