3

I'm not sure about the title of the question, but basically I'm curious of how to create a visitor-like function that can operate on certain types on a collection that correctly uses type inference.

For example, a collection contains objects that inherit from a single base class (Base). Some operations apply only to specific child classes (e.g., FooBar inherits from Base).

One implementation can be

template<class T, class F>
void visit(F f)
{
  for (auto c : the_collection) {
    if (auto t = dynamic_cast<T*>(c)) {
      f(t);
    }
  }
}

The problem here is that invoking such function will need to specify the class type FooBar twice:

visit<FooBar>([](FooBar* f) { f->some_method(); });

I'd like to use type inference, so I can just write visit([](FooBar* f) ..., but can't manage to get the right template.

For example:

template<class T>
using Visitor = std::function<void(T*)>;
template<class T>
void visit(const Visitor<T>& f)
{
  for (auto c : the_collection)
    if (auto t = dynamic_cast<T*>(c))
      f(t);
}

works with visit<FooBar>([](FooBar*) ... but not with visit([](FooBar*) ....

no matching overloaded function found

void visit(const std::function<void(T *)> &)': could not deduce template argument for 'const std::function<void(T *)> &' from '{....}::<lambda_2c65d4ec74cfd95c8691dac5ede9644d>

Is it possible to define a template that can infer the types in this way or the language specification doesn't allow to do that?

Community
  • 1
  • 1
cbuchart
  • 10,847
  • 9
  • 53
  • 93
  • Possible duplicate of [Constructing std::function argument from lambda](https://stackoverflow.com/questions/54664303/constructing-stdfunction-argument-from-lambda). You also might wanna have a look at the numerous other questions on exactly the same problem, e.g., this one: https://stackoverflow.com/questions/55784120/how-to-define-template-parameters-for-a-generic-lambda-argument ;) – Michael Kenzel Apr 23 '19 at 11:35
  • Why not use regular visitor pattern if you have polymorphic class already? – Jarod42 Apr 23 '19 at 11:43
  • upcasting almost always means trouble and design flaws, but I'm sure you absolutely know what you are doing :) – local-ninja Apr 23 '19 at 16:00

3 Answers3

2

The easiest way I've found to do this so far (but not exactly what I've been looking for) is to define the visitor using the second form indicated in the question, and to declare the lambda parameter as auto:

template<class T>
using Visitor = std::function<void(T*)>;
template<class T>
void visit(const Visitor<T>& f)
{
  for (auto c : the_collection)
    if (auto t = dynamic_cast<T*>(c))
      f(t);
}

// ...

visit<FooBar>([](auto f) { f->some_method(); });
cbuchart
  • 10,847
  • 9
  • 53
  • 93
2

You tagged C++17 so you can use deduction guides for std::function.

So what about something as follows ?

template <typename>
struct first_arg_type;

template <typename R, typename T0, typename ... Ts>
struct first_arg_type<std::function<R(T0, Ts...)>>
 { using type = T0; };

template <typename F>
void visit (F const & f)
 {
   using T = typename first_arg_type<decltype(std::function{f})>::type;

   for (auto c : the_collection)
      if (auto t = dynamic_cast<T>(c))
         f(t);
 }

Observe that, instead of the custom type trait first_arg_type you could use the standard type first_argument_type in std::function, so

   using T = typename decltype(std::function{f})::first_argument_type;

Unfortunately std::function::first_argument_type is deprecated starting from C++17 and will be removed from C++20.

max66
  • 65,235
  • 10
  • 71
  • 111
0

We can achieve the desired syntax of visit([](FooBar*){ /*...*/ }); by deducing the type of the call operator of the lambda.

template <typename Element, typename Class, typename Parameter>
void call(Element element, Class *callable, void(Class::*function)(Parameter) const) {
  if (auto parameter = dynamic_cast<Parameter>(element)) {
    (callable->function)(parameter);
  }
}

template <typename Functor>
void visit(Functor &&functor) {
  for (auto element : the_collection) {
    call(element, &functor, &Functor::operator());
  }
}
Indiana Kernick
  • 5,041
  • 2
  • 20
  • 50