I have a set of Writer classes each with a different implementation. I have a list of writers that offers the same interface. Calling a method on the list should invoke the same method on each of the elements in the list (composite design pattern). To avoid the overhead of virtual dispatching the writers are stored in a std::variant, and the list iterates over a vector of variants and uses a std::visit and std::invoke to invoke a method on each of the writers.
Given the following piece of C++17 code as a working example implementation:
#include <cstdio>
#include <variant>
#include <string>
#include <functional>
class Writer1
{
public:
void writeX(int i) { printf("Writer1::writeX(%d)\n",i); }
void writeY(int i,const std::string &s) { printf("Writer1::writeY(%d,%s)\n",i,s.c_str()); }
// ... many more writeXYZ functions
};
class Writer2
{
public:
void writeX(int i) { printf("Writer2::writeX(%d)\n",i); }
void writeY(int i,const std::string &s) { printf("Writer2::writeY(%d,%s)\n",i,s.c_str()); }
// ... many more writeXYZ functions
};
class WriterList
{
using WriterVariant = std::variant<Writer1,Writer2>;
#define FORALL(name) \
template<class... As> \
void forall_##name(As&&... args) \
{ \
for (auto &v : m_writers) \
{ \
std::visit([&](auto &&o) { \
using T = std::decay_t<decltype(o)>; \
std::invoke(&T::name,o,std::forward<As>(args)...); \
},v); \
} \
}
FORALL(writeX);
FORALL(writeY);
// ... many more writeXYZ functions
public:
void add(WriterVariant &&v) { m_writers.push_back(std::move(v)); }
void writeX(int i) { forall_writeX(i); }
void writeY(int i,const std::string &s) { forall_writeY(i,s); }
// ... many more writeXYZ functions
private:
std::vector<WriterVariant> m_writers;
};
int main()
{
WriterList wl;
wl.add(Writer1());
wl.add(Writer2());
wl.writeX(42);
wl.writeY(2,"Hello");
}
The FORALL
macro is only needed for the member reference &T::name
. I would prefer to pass that as a (template) parameter and avoid the ugly macro. It this somehow possible?