具有lambdas的内联变体访问者访问者

时间:2018-01-05 16:43:33

标签: c++ c++11

有一位经典访客:

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形式)

4 个答案:

答案 0 :(得分:1)

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

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

}; // calss 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(),但可以通过添加只调用callables的visit方法进行修改。

希望在实现方面明白这一点很清楚,简而言之,它是一个继承自所有不同lambdas的类,因此它将所有主要函数组合在一个类中。

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

答案 1 :(得分:1)

我将在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的变体式访问者。

然后我们重写Object。它暴露了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时选择了哪一个。

Live example

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

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

答案 2 :(得分:0)

这是另一种选择。构造一个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;

然后你可以用函数指针,lambdas或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

有用

答案 3 :(得分:-1)

如果你想要更简单的东西,你可以从这个草图代码中解决问题,(或者你可以使用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;
}