I've been looking at getting back into C++11 after a few years and came across the idea of implementing the 'inline' visitor pattern. The idea is that you can specify your visitor class locally in the code using lambdas, function pointers and/or std::function
. Missing functions would default to a default implementation (D
in the code below). I would be interested in the opinion of any C++11 gurus out there about improvements that I can make, or if there is a much better way to do this. Over time I'd like to formalize this simple idea into a much better library, so any contributions would be welcome.
My questions:
- Is there a better way to extract the type of the first argument of the function that using my
arg_type
template - Is there a better way of passing in the default visit implementation other than as a functor in a
template template
argument? - To me having the nodes reference an interface of the visitor exclusively for their type (
ivisitor<mynodetype>
) seems sensible, but usually the visitor pattern doesn't separate thevisit()
implementations into separate interfaces, so I'm wondering if there is a downside to this idea? - Can I avoid storing my
visit()
implementations in astd::function
datamember?
#include <functional>
#inline-visitor.hpp
template<typename T>
struct arg_type :
public arg_type<decltype(&T::operator())> {};
template<typename T>
struct arg_type<void(*)(T&)> :
public arg_type<void(T&)> {};
template<typename T, typename C>
struct arg_type<void(C::*)(T&) const > :
public arg_type<void(T&)> {};
template<typename T>
struct arg_type<void(T&)> {
typedef T type;
};
template<typename T>
class ivisitor {
public:
virtual ~ivisitor() = default;
virtual void visit(T& node) = 0;
};
template<typename T, template<typename> class D>
class visitor : public ivisitor<T>{
public:
visitor():
f_(D<T>()) {
}
void visit(T& node) override {
if(f_) {
f_(node);
}
}
void set(std::function<void(T&)> f) {
f_ = f;
}
private:
std::function<void(T&)> f_;
};
template<template<typename> class D, typename ...T>
class compound_visitor : public visitor<T, D>... {
public:
compound_visitor() = default;
template<typename ...F>
compound_visitor(F... f) {
set(f...);
}
template<typename F, typename ...Fs>
void set(F f, Fs... fs) {
set(f);
set(fs...);
}
template<typename U>
void visit(U& node) {
this->visitor<U, D>::visit(node);
}
template<typename F>
void set(F f) {
this->visitor<typename arg_type<F>::type, D>::set(f);
}
};
template<typename C, typename ...F>
auto make_compound_visitor(F... f) -> C {
return c(f...);
}
Example code follows:
main.cpp
#include "inline_visitor.hpp"
#include <iostream>
template<typename T>
struct output_default {
void operator()(T&) {
std::cout << "default";
}
};
class node1 {
public:
void accept(ivisitor<node1>& v) {
v.visit(*this);
}
};
class node2 {
public:
void accept(ivisitor<node2>& v) {
v.visit(*this);
}
};
class node3 {
public:
void accept(ivisitor<node3>& v) {
v.visit(*this);
}
};
class node4 {
public:
void accept(ivisitor<node4>& v) {
v.visit(*this);
}
};
int main(int argc, char** argv) {
typedef compound_visitor<output_default, node1, node2, node3, node4> concrete_visitor;
auto v = make_compound_visitor<concrete_visitor>(
[](node1& node) -> void { std::cout << "n1";},
std::function<void(node2&)>([](node2& node) -> void { std::cout << "n2";}),
+[](node3& node) -> void { std::cout << "n3";}
);
node1 n1;
node2 n2;
node3 n3;
node4 n4;
n1.accept(v);
n2.accept(v);
n3.accept(v);
n4.accept(v);
return 0;
}