Reputation: 1103
There is a classic visitor:
struct Visitor
{
virtual ~Visitor() = default;
virtual void visit(X& x) {}
virtual void visit(Y& y) {}
virtual void visit(Z& z) {}
};
and
struct Object
{
virtual void accept(Visitor& visitor) { visitor.visit(*this); }
};
X,Y,Z are derived from Object.
I want to make the visitor right on the spot, with lambdas, like this:
auto visitor = make_visitor<Visitor>
(
[](X& x) {do operations on x},
//for Y do not want to do anything. default implementation is suitable.
[](Z& z) {do operations on z}
);
object.accept(visitor);
are there any ideas how to implement make_visitor?
(I read https://accu.org/index.php/journals/2160 and it's great - I just wanna a shorter and more convenient form of make_visitor)
Upvotes: 3
Views: 1557
Reputation: 13288
I wanted to do the same thing a few years ago and i came up with this:
template<typename ResultType, typename ... Callables>
class visitor;
template<typename ResultType, typename Callable, typename ... Callables>
class visitor<ResultType, Callable, Callables...>
: public Callable, public visitor<ResultType, Callables...>::type
{
public:
using type = visitor;
public:
visitor(Callable callable, Callables... callables)
:
Callable(callable), visitor<ResultType, Callables...>::type(callables...)
{
}
public:
using Callable::operator();
using visitor<ResultType, Callables...>::type::operator();
}; // class visitor
template <typename ResultType, typename Callable>
class visitor<ResultType, Callable>
: public Callable, public boost::static_visitor<ResultType>
{
public:
using type = visitor;
public:
visitor(Callable callable)
:
Callable(callable)
{
}
public:
using Callable::operator();
}; // class visitor
template<typename ResultType = void, typename ... Callables>
typename visitor<ResultType, Callables...>::type
make_visitor(Callables... callables)
{
return (typename visitor<ResultType, Callables...>::type(callables...));
}
Instead of calling a method called visit
it uses the call operator operator()
but can be modified by adding a visit
method that just calls the callables.
Hopefully it's clear enough to understand in terms of implementation, in a nutshell it's a class that inherits from all the different lambdas so it combines all of their main function in one class.
At the time it was mainly and heavily inspired from this answer: https://stackoverflow.com/a/18731900/1147772.
Upvotes: 2
Reputation: 875
Here is an alternative. Construct a compound_visitor
specifying a default functor, and the types you want to support :
template<typename T>
struct output_default {
void operator()(T&) {
std::cout << "default";
}
};
typedef compound_visitor<output_default, node1, node2, node3, node4> concrete_visitor;
Then you can override some of the node type visit
methods with either function pointers, lambdas or std::function, note I dont provide a function for node4
so it defaults to the implementation provided by output_default<node4>
:
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";}
);
Full code here :
#include <iostream>
#include <functional>
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, template<typename> typename D>
class visitor {
public:
visitor():
f_(D<T>()) {
}
void visit(T& node) {
if(f_) {
f_(node);
}
}
void set(std::function<void(T&)> f) {
f_ = f;
}
private:
std::function<void(T&)> f_;
};
template<template<typename> typename D, typename ...T>
class compound_visitor : public visitor<T, D>... {
public:
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 set(C& c, F f) {
c.set(f);
}
template<typename C, typename F, typename ...Fs>
auto set(C& c, F f, Fs... fs) {
set(c, f);
set(c, fs...);
}
template<typename C, typename ...F>
auto make_compound_visitor(F... f) {
C c;
set(c, f...);
return c;
}
template<typename T>
struct output_default {
void operator()(T&) {
std::cout << "default";
}
};
// usage
class node1;
class node2;
class node3;
class node4;
typedef compound_visitor<output_default, node1, node2, node3, node4> concrete_visitor;
class node1 {
public:
void accept(concrete_visitor& v) {
v.visit(*this);
}
};
class node2 {
public:
void accept(concrete_visitor& v) {
v.visit(*this);
}
};
class node3 {
public:
void accept(concrete_visitor& v) {
v.visit(*this);
}
};
class node4 {
public:
void accept(concrete_visitor& v) {
v.visit(*this);
}
};
int main(int argc, char** argv) {
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;
}
Code above outputs :
n1n2n3default
I've put this code into github. I think it could be useful to someone https://github.com/the4thamigo-uk/inline-visitor
Upvotes: 0
Reputation: 275730
I'll do this in C++17.
First we start with the idea of override:
template<class T>
struct override_helper { using type=T; };
template<class T>
using override_helper_t = typename override_helper<T>::type;
template<class R, class...Args>
struct override_helper<R(*)(Args...)> {
struct type {
R(*f)(Args...);
R operator()(Args...args)const { return f(std::forward<Args>(args)...); }
type(R(*in)(Args...)):f(in) {}
};
};
template<class R, class...Args>
struct override_helper<R(&)(Args...)>:override_helper<R(*)(Args...)> {
using override_helper<R(*)(Args...)>::override_helper;
};
template<class...Fs>
struct override:override_helper_t<Fs>... {
using override_helper_t<Fs>::operator()...;
override(Fs...fs):override_helper_t<Fs>(std::move(fs))... {}
};
now we can do this:
auto f = override(
[](X& x) {do operations on x},
[](Z& z) {do operations on z},
[](auto&) {default operation goes here}
);
and f
is now a variant-style visitor that accepts X, Y and Z.
We then rewrite Object. Either it exposes a variant
, or we fake it.
template<class D, class Sig>
struct function_invoker;
template<class D, class R, class...Args>
struct function_invoker<D, R(Args...)> {
R operator()(Args...args)const {
return f(
static_cast<D const*>(this)->get(),
std::forward<Args>(args)...
);
}
template<class F>
function_invoker( F&& fin ):
f([](void* ptr, Args&&...args)->R{
auto* pf = static_cast<std::remove_reference_t<F>*>(ptr);
return (*pf)(std::forward<Args>(args)...);
})
{}
private:
R(*f)(void*, Args&&...) = 0;
};
template<class...Sigs>
struct function_view :
private function_invoker<function_view<Sigs...>, Sigs>...
{
template<class D, class Sig>
friend class function_invoker;
using function_invoker<function_view<Sigs...>, Sigs>::operator()...;
template<class F,
std::enable_if_t< !std::is_same<std::decay_t<F>, function_view>{}, bool> =true
>
function_view( F&& fin ):
function_invoker<function_view<Sigs...>, Sigs>( fin )...,
ptr((void*)std::addressof(fin))
{}
explicit operator bool() const { return ptr; }
private:
void* get() const { return ptr; }
void* ptr = 0;
};
this is a multiple-signature callable function pointer type erasure.
using Visitor = function_view< void(X&), void(Y&), void(Z&) >;
Visitor
is now a view type for any callable that can be invoked with any of an X, Y or Z reference.
struct Object
{
virtual void accept(Visitor visitor) = 0;
};
template<class D>
struct Object_derived:Object {
virtual void accept(Visitor visitor) final override {
visitor(*static_cast<D*>(this));
}
};
struct X:Object_derived<X> {};
struct Y:Object_derived<Y> {};
struct Z:Object_derived<Z> {};
now you can pass [](auto&){}
to Object::accept
and it compiles.
We then hook override up, and we pass in a callable with suitable overrides.
function_view
stores a pointer to the override object and a function pointer saying how to invoke each override.
Which one is picked when you implement accept
.
Everything I have done here can be done in c++11, but is far easier in c++17, so I did it there as proof of concept.
function_view probably wants SFINAE friendly ctor that detects if its argument satisfies all of the signatures, but I got lazy.
Upvotes: 1
Reputation: 875
If you want something a bit simpler you can work something out from this sketch code, (alternatively you can use std::function rather than raw function pointers, and I didnt handle the default implementation part but that is a logical extension I think) :
#include <iostream>
struct X{};
struct Y{};
struct Z{};
struct Visitor
{
Visitor(void (*xf)(X&), void (*yf)(Y&), void (*zf)(Z&)):
xf_(xf), yf_(yf), zf_(zf) {
}
virtual ~Visitor() = default;
void visit(X& x) {xf_(x);}
void visit(Y& y) {yf_(y);}
void visit(Z& z) {zf_(z);}
private:
void (*xf_)(X& x);
void (*yf_)(Y& x);
void (*zf_)(Z& x);
};
template<typename T>
T make_visitor(void (*xf)(X&),void (*yf)(Y&),void (*zf)(Z&)) {
return T(xf, yf, zf);
}
int main(int argc, char** argv) {
auto visitor = make_visitor<Visitor>
(
[](X& x) {std::cout << "x";},
[](Y& y) {std::cout << "y";},
[](Z& z) {std::cout << "z";}
);
X x;
Y y;
Z z;
visitor.visit(x);
visitor.visit(y);
visitor.visit(z);
return 0;
}
Upvotes: -1