Reputation: 11555
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
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
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
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