【问题标题】:Inline-variant-visitor visitor with lambdas带有 lambda 的内联变量访问者访问者
【发布时间】:2018-01-05 16:43:32
【问题描述】:

有一位经典的访客:

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

【问题讨论】:

标签: c++ c++11


【解决方案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...));
}

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

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

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

【讨论】:

    【解决方案2】:

    我将在 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&amp;){} 传递给Object::accept 并编译。

    然后我们连接覆盖,并传入一个带有合适覆盖的可调用对象。

    function_view 存储一个指向覆盖对象的指针和一个说明如何调用每个覆盖的函数指针。

    实现accept时会选择哪一个。

    Live example.

    我在这里所做的所有事情都可以在 中完成,但在 中要容易得多,所以我在那里做了概念证明。

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

    【讨论】:

    • 我认为你的 override_helper 类可以在 17 中简化。我通常看到的重载访问者 lambda 的示例是:template&lt;class... Ts&gt; struct overloaded : Ts... { using Ts::operator()...; };template&lt;class... Ts&gt; overloaded(Ts...) -&gt; overloaded&lt;Ts...&gt;;
    • @0x5453 我希望能够重载函数指针,并且不能从函数指针继承。 ;)
    【解决方案3】:

    这是另一种选择。构造一个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&lt;node4&gt; 提供的实现:

      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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2018-02-09
        • 1970-01-01
        • 1970-01-01
        • 2022-12-04
        • 2019-03-05
        • 2021-03-16
        • 1970-01-01
        • 1970-01-01
        • 2017-07-10
        相关资源
        最近更新 更多