innochenti
innochenti

Reputation: 1103

Inline-variant-visitor visitor with lambdas

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

Answers (4)

Drax
Drax

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

the4thamigo_uk
the4thamigo_uk

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

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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.

Live example.

Everything I have done here can be done in , but is far easier in , 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

the4thamigo_uk
the4thamigo_uk

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

Related Questions