Home > OS >  How to deduce the template argument when storing a lambda-templated class as a member of another cla
How to deduce the template argument when storing a lambda-templated class as a member of another cla

Time:01-11

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

Demo


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

Demo

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

  • Related