【问题标题】:How to deduce the template argument when storing a lambda-templated class as a member of another class?将 lambda 模板类存储为另一个类的成员时如何推断模板参数?
【发布时间】:2023-01-11 04:23:10
【问题描述】:

我有一个关于将 lambda 模板对象存储为类成员的问题。 Invoker 类是一个存储任意 lambda 函数的模板化类。我想在另一个类 Worker 中存储 Invoker 的实例。但是,当Invoker用作类成员时,我不知道如何填写模板参数TCallback。它不像 main 函数中的第一行那样推导。如 cmets 所示,我试图在 Worker 中的某处定义一个 lambda,并将其传递给 Invoker 类型的成员。

我尝试过的是使用类方法的decltype,但它不能像通用 lambda 一样被调用——它需要类对象上下文才能运行。

非常感谢任何想法和一些解决方法。

谢谢你。

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

【问题讨论】:

    标签: c++ templates lambda metaprogramming template-meta-programming


    【解决方案1】:

    问题在于:

    Invoker<decltype(&Worker::some_callback)> invoker{&Worker::some_callback};
    

    some_callback 是一个非静态成员函数。当你调用它时,一个隐含的this指针实际上被传递给它。所以当你通过成员函数指针调用它时,需要一个Worker的对象(它在你的Invoker类中不存在。


    一种解决方法是使用绑定表达式将 this 指针与 some_callback 绑定:

    std::bind(&Worker::some_callback, this)
    

    然后可以将结果类型转换为 std::function&lt;void()&gt; 并存储在 Worker.invoker 中:

    Invoker<std::function<void()>> invoker{std::bind(&Worker::some_callback, this)};
    

    Demo


    另一种解决方案是改用静态成员函数。通过使用静态函数,您将不再需要传递 this 指针,您可以直接使用 &amp;Worker::some_callback 初始化 invoker:

    Invoker<std::function<void()>> invoker{&Worker::some_callback};
    

    Demo

    【讨论】:

    • 谢谢!将成员函数绑定到调用者的模板只是我的尝试之一。我的目标是将任意 lambda 绑定到 Invoker 的模板,并将 Invoker 存储为成员(不使用 std::function)。你认为这可能吗?
    • @YuanyiWu 只能绑定一个类型到invoker,因为invoker的确切类型不能改变。要绑定多个类型,最好的方法是将Worker 设为模板类。
    • @YuanyiWu 另请注意,根据您的Invoker,cb 不能接受任何参数,并且会丢弃任何返回值,因此传递给它的任何函数实际上都是void() 类型。
    • 是的。回调是概念性的,所以它的签名在这里并不重要。毕竟,lambda 的实际类型是编译器私有的。
    • 对于将Worker类做成模板的建议,其实我并不想绑定多次回调。我只想存储一次回调(在初始化时,可能在构造函数中)。问题是 Invoker 的模板类型是不可扣除的。
    【解决方案2】:

    你真的需要使用 remplates 吗?这有效:

    #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
    ";
      }
    
       // 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
    ";});
      Worker worker;
      worker.invoker.invoke();
      worker.invoker1.invoke();
      return 0;
    }
    

    您也可以使用模板来完成,但是您需要 lambda 的模板推导指南(这是 C++17 功能):

    #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
    ";
      }
    
      // 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
    ";});
      Worker worker;
      worker.invoker.invoke();
      return 0;
    }
    

    类内的 Lambda 只能与 std::function 一起使用,因为不可能将 auto 用于类成员,并且您不能以任何其他方式定义其类型。

    对于成员函数,为了简化其使用,您甚至可以创建特殊的构造函数和推导指南。像这样:

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

    【讨论】:

    • 谢谢!使用 std::function 绝对可行,但我也想学习如何处理类型推导对班级成员不起作用的情况。
    • @YuanyiWu 添加了一些变化...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-06
    • 2012-06-13
    • 2014-05-23
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多