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?
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.
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(); });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With