Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 366
cbuchart Avatar asked Sep 21 '26 06:09

cbuchart


2 Answers

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.

like image 143
max66 Avatar answered Sep 24 '26 01:09

max66


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(); });
like image 26
cbuchart Avatar answered Sep 23 '26 23:09

cbuchart



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!