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();
}
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.