I have a question about storing a lambda-templated object as a class member. The Invoker
class is a templated class storing an arbitrary lambda function. I want to store an instance of an Invoker
in another class, Worker
. However, I do not know how to fill the template argument TCallback
when the Invoker
is used as a class member. It does not deduce like the first line in the main
function. As shown in the comments, I am trying to define a lambda somewhere in Worker
and pass it to its member of the type Invoker
.
What I have tried is to use decltype
of a class method but it could not be invoked like a generic lambda - it needs the class object context to run.
Much appreciated for any ideas and maybe some workarounds.
Thank you.
#include <iostream>
template <typename TCallback>
struct Invoker {
explicit Invoker(TCallback&& cb) : cb(cb) {}
TCallback cb;
void invoke() {
cb();
}
};
struct Worker {
void some_callback() {
std::cout << "callback in worker\n";
}
// Not working: some_callback is a member function and can only be called with the context object
Invoker<decltype(&Worker::some_callback)> invoker{&Worker::some_callback};
// How to make a something like this?
// auto lambda_in_class = [&]{some_callback()};
// Invoker<decltype(lambda_in_class)> invoker{lambda_in_class};
};
int main() {
Invoker invoker([]{std::cout << "invoker\n";});
Worker worker;
worker.invoker.invoke();
return 0;
}
CodePudding user response:
The problem with:
Invoker<decltype(&Worker::some_callback)> invoker{&Worker::some_callback};
is that some_callback
is a non-static member function. When you call it, an implicit this
pointer is actually passed to it. So when you call it through the member function pointer, an object of Worker
is required(which does not exist in your Invoker
class.
One workaround is to use a bind expression to bind a this
pointer with the some_callback
:
std::bind(&Worker::some_callback, this)
And the result type can then be converted to a std::function<void()>
and stored in Worker.invoker
:
Invoker<std::function<void()>> invoker{std::bind(&Worker::some_callback, this)};
Another solution is to use a static member function instead. By using a static function, you would not need to pass a this
pointer anymore, and you can initialize invoker
directly with &Worker::some_callback
:
Invoker<std::function<void()>> invoker{&Worker::some_callback};
CodePudding user response:
Do you really need to use remplates? This works:
#include <iostream>
#include <functional>
struct Invoker {
explicit Invoker(std::function<void()> cb) : cb(cb) {}
std::function<void()> cb;
void invoke() {
cb();
}
};
struct Worker {
void some_callback() {
std::cout << "callback in worker\n";
}
// Not working: some_callback is a member function and can only be called with the context object
Invoker invoker{std::bind(&Worker::some_callback, this)};
// How to make a something like this?
std::function<void()> lambda_in_class = [&]{some_callback();};
Invoker invoker1{lambda_in_class};
};
int main() {
Invoker invoker([]{std::cout << "invoker\n";});
Worker worker;
worker.invoker.invoke();
worker.invoker1.invoke();
return 0;
}
You can do it with templates as well, but you will need template deduction guide (which is C 17 feature) for lambdas:
#include <iostream>
#include <functional>
template <typename TCallback>
struct Invoker {
explicit Invoker(TCallback cb) : cb(cb) {}
TCallback cb;
void invoke() {
cb();
}
};
template<typename T>
Invoker(T) -> Invoker<T>;
struct Worker {
void some_callback() {
std::cout << "callback in worker\n";
}
// Not working: some_callback is a member function and can only be called with the context object
Invoker<decltype(std::bind(&Worker::some_callback, static_cast<Worker *>(0)))> invoker{std::bind(&Worker::some_callback, this)};
// How to make a something like this?
// auto lambda_in_class = [&]{some_callback()};
// Invoker<decltype(lambda_in_class)> invoker{lambda_in_class};
};
int main() {
Invoker invoker([]{std::cout << "invoker\n";});
Worker worker;
worker.invoker.invoke();
return 0;
}
Lambda inside class will only work with std::function
, since it is impossible to use auto
for class member and you can't define its type any other way.
For member functions, to simplify its usage you even can create special constructor and deduction guide. Like this:
// Add constructor to Invoker definition
template<typename T>
Invoker(void (T::*func)(), T* obj) : cb(std::bind(func, obj)) {}
// Add template guide after Invoker definition
template<typename T>
Invoker(void (T::*func)(), T* obj) -> Invoker<decltype(std::bind(func, obj))>;
// Now you can declare invoker in Worker like this
decltype(Invoker(&Worker::some_callback, static_cast<Worker *>(0))) invoker{&Worker::some_callback, this};
// Or outside, like this
Worker worker;
auto invoker = Invoker{&Worker::some_callback, &worker};