带有 lambda 的内联变体访问者

Inline-variant-visitor visitor with lambdas

本文关键字:访问者 lambda 带有      更新时间:2023-10-16

有一个经典的访客:

struct Visitor
{
virtual ~Visitor() = default;
virtual void visit(X& x) {}
virtual void visit(Y& y) {}
virtual void visit(Z& z) {}
};

struct Object
{
virtual void accept(Visitor& visitor) { visitor.visit(*this); }
};

X,Y,Z 派生自 Object。

我想让访客当场,用lambdas,像这样:

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);

有什么想法如何实施make_visitor吗?

(我读了 https://accu.org/index.php/journals/2160,很棒 - 我只是想要一种更短、更方便的make_visitor形式)

几年前我想做同样的事情,我想出了这个:

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

它不是调用名为visit的方法,而是使用调用运算符operator()但可以通过添加仅调用可调用对象的visit方法来修改。

希望它足够清晰,可以在实现方面理解,简而言之,它是一个继承自所有不同 lambda 的类,因此它将它们的所有主要函数组合在一个类中。

当时,它主要受到这个答案的启发:https://stackoverflow.com/a/18731900/1147772。

我将在 C++17 中执行此操作。

首先,我们从覆盖的想法开始:

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

现在我们可以这样做:

auto f = override(
[](X& x) {do operations on x},
[](Z& z) {do operations on z},
[](auto&) {default operation goes here}
);

f现在是接受 X、Y 和 Z 的变体样式访客。

然后我们重写对象。 要么它暴露了一个variant,要么我们伪造它。

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;
};

这是一种多签名可调用函数指针类型擦除。

using Visitor = function_view< void(X&), void(Y&), void(Z&) >;

Visitor现在是可以使用任何 X、Y 或 Z 引用调用的任何可调用对象的视图类型。

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> {};

现在,您可以将[](auto&){}传递给Object::accept并编译。

然后,我们挂钩覆盖,并传入具有适当覆盖的可调用对象。

function_view存储指向重写对象的指针和一个指示如何调用每个重写的函数指针。

实现accept时选择哪一个。

活生生的例子。

我在这里所做的一切都可以在 c++11 中完成,但在 c++17 中要容易得多,所以我在那里做了它作为概念证明。

function_view可能想要 SFINAE 友好的 ctor 来检测其参数是否满足所有签名,但我懒惰了。

这是一个替代方案。构造一个compound_visitor,指定默认函子和要支持的类型:

template<typename T>
struct output_default {
void operator()(T&) {
std::cout << "default";
}
};
typedef compound_visitor<output_default, node1, node2, node3, node4> concrete_visitor;

然后你可以用函数指针、lambda 或 std::function 覆盖一些节点类型visit方法,注意我没有为node4提供函数,所以它默认为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";}
);

完整的代码在这里:

#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;
}

上面的代码输出:

n1n2n3default

我已经把这段代码放到了github中。我认为它可能对某人有用 https://github.com/the4thamigo-uk/inline-visitor

如果你想要更简单的东西,你可以从这个草图代码中找出一些东西,(或者你可以使用 std::function 而不是原始函数指针,我没有处理默认的实现部分,但我认为这是一个逻辑扩展):

#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;
}