3

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)

innochenti
  • 1,093
  • 2
  • 8
  • 23
  • One trick I have seen before: https://dev.to/tmr232/that-overloaded-trick-overloading-lambdas-in-c17 – 0x5453 Jan 05 '18 at 18:45
  • It’s hard to get all the details like `constexpr`, `noexcept`, allowing non-lambda potentially-final function objects and raw function pointers, etc. etc. If you care about those, your best bet is to read [p0051](https://wg21.link/p0051) and either make sure you handle it, or just use [their implementation](https://github.com/viboes/std-make/blob/master/include/experimental/fundamental/v3/functional/overload.hpp). I hope it makes it into C++ eventually. – Daniel H Jan 05 '18 at 19:56

4 Answers4

2

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.

Drax
  • 12,682
  • 7
  • 45
  • 85
1

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I think your `override_helper` classes can be simplified in 17. The example I usually see for overloading visitor lambdas is: `template struct overloaded : Ts... { using Ts::operator()...; };` `template overloaded(Ts...) -> overloaded;` – 0x5453 Jan 05 '18 at 18:42
  • @0x5453 I want to be able to overload function pointers, and you cannot inherit from function pointers. ;) – Yakk - Adam Nevraumont Jan 05 '18 at 19:03
0

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

the4thamigo_uk
  • 835
  • 4
  • 8
-1

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;
}
the4thamigo_uk
  • 835
  • 4
  • 8