denomme
denomme

Reputation: 35

C++: Least painful way to write a Visitor to use in apply std::visit for a templated function?

I'm using a heavily templated library with a function that has multiple template arguments. I'd like to create a dynamic wrapper for this function by creating a class whose members will be the arguments to this templated function. I stumbled across std::visit, which requires a Visitor object, but it seems quite painful to write a visitor helper class to wrap the function, even when there are only a handful of possible template classes in consideration. Is there a less painful way to generate the Visitor?

Example with a manually implemented visitor helper:

#include <iostream>
#include <variant>

namespace NotMyLibrary
{
class A
{
    public:
    void execute()
    {
        std::cout << "From A!" << std::endl;
    }
};

class B
{
    public:
    void execute()
    {
        std::cout << "From B!" << std::endl;
    }
};

class C
{
    public:
    void apply()
    {
        std::cout << "From C!" << std::endl;
    }
};

class D
{
    public:
    void apply()
    {
        std::cout << "From D!" << std::endl;
    }
};

template<typename AorB, typename CorD>
void library_function_with_static_dependencies(AorB f, CorD g)
{
    size_t num_iterations = 1; //but really 1'000'000+
    for(size_t i=0; i < num_iterations; ++i)
    {
        f.execute();
        g.apply();
        std::cout << std::endl;
    }
}
} //end namespace NotMyLibrary


class VisitHelper
{
    public:
    void operator()(NotMyLibrary::A f, NotMyLibrary::C g) {NotMyLibrary::library_function_with_static_dependencies(f,g);}
    void operator()(NotMyLibrary::A f, NotMyLibrary::D g) {NotMyLibrary::library_function_with_static_dependencies(f,g);}   
    void operator()(NotMyLibrary::B f, NotMyLibrary::C g) {NotMyLibrary::library_function_with_static_dependencies(f,g);}   
    void operator()(NotMyLibrary::B f, NotMyLibrary::D g) {NotMyLibrary::library_function_with_static_dependencies(f,g);}   

};


class DynamicFunctionExecutor
{
    public:
    std::variant<NotMyLibrary::A, NotMyLibrary::B> f;
    std::variant<NotMyLibrary::C, NotMyLibrary::D> g;
    
    void dispatch()
    {
        VisitHelper vh{};
        std::visit(vh, f, g);
    }
};



int main()
{
    using namespace NotMyLibrary;
    DynamicFunctionExecutor e;
    e.f = A();
    e.g = C();
    e.dispatch();
    
    e.f = B();
    e.g = D();
    e.dispatch();
    
}

Upvotes: 0

Views: 93

Answers (1)

Artyer
Artyer

Reputation: 40881

You can make operator() a template:


class VisitHelper
{
    public:
    /* Replace:
    void operator()(NotMyLibrary::A f, NotMyLibrary::C g) {NotMyLibrary::library_function_with_static_dependencies(f,g);}
    void operator()(NotMyLibrary::A f, NotMyLibrary::D g) {NotMyLibrary::library_function_with_static_dependencies(f,g);}   
    void operator()(NotMyLibrary::B f, NotMyLibrary::C g) {NotMyLibrary::library_function_with_static_dependencies(f,g);}   
    void operator()(NotMyLibrary::B f, NotMyLibrary::D g) {NotMyLibrary::library_function_with_static_dependencies(f,g);}   
     */
    template<typename AorB, typename CorD>
    void operator()(AorB f, CorD g) {
        NotMyLibrary::library_function_with_static_dependencies(f,g);
    }
};

// ...

    void dispatch()
    {
        std::visit(VisitHelper{}, f, g);
    }

Which you can further replace with a generic lambda:

    void dispatch()
    {
        std::visit([](auto f, auto g) {
            NotMyLibrary::library_function_with_static_dependencies(f, g);
        }, f, g);
    }

... Which is the same as creating an object of a class type with a templated operator(), like VisitHelper, and passing it to std::visit{}.

Most of the time you use std::visit you should use a generic lambda. Usually, you want const auto& to prevent copies.

Upvotes: 4

Related Questions