cbuchart
cbuchart

Reputation: 11555

Infering template type from function argument

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?

Upvotes: 3

Views: 261

Answers (3)

max66
max66

Reputation: 66200

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.

Upvotes: 3

Indiana Kernick
Indiana Kernick

Reputation: 5331

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

Upvotes: 0

cbuchart
cbuchart

Reputation: 11555

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

Upvotes: 2

Related Questions