【问题标题】:Member Function Pointer Template Substitution Fails成员函数指针模板替换失败
【发布时间】:2020-12-08 01:00:13
【问题描述】:

我有以下代码:

class A{
public:
    A() {}

    void foo(const B& b) {
       int a = b.a();
    }
};

template<class T, typename ... ARGS>
std::function<void()> * invoke(T *t, void(T::* fn)(ARGS...), ARGS... args) {
    //Create a new std::function on the heap to be executed later
    std::function<void()> *f = new std::function<void()>([=]() { (t->*fn)( args... ); });

    return f;
}

int main(void) {
    A myA;
    B myB(5, 6, 7);

    std::function<void()> *fn = invoke(&myA, &A::foo, myB);
}

其目的是能够从堆上的成员函数指针(和对象指针)创建通用std::function,以供以后执行。

我的问题是,编译器似乎无法弄清楚如何正确扩展调用模板,我收到以下错误:

template argument deduction/substitution failed:
inconsistent parameter pack deduction with 'const B&' and 'B'

我希望我的 invoke() 函数的语义保持不变(即对象 *、成员函数 *、参数...)。

有没有办法在保持这些语义的同时仍然允许编译器找出正确的模板推导?

谢谢!

编辑

如果我执行以下操作,我可以让它工作:

template<class T, typename F, typename ... ARGS>
std::function<void()> * invoke(T *t, F const &fn, ARGS... args) {
    std::function<void()> *f = new std::function<void()>([=]() { (t->*fn)( args... ); });

    return f;
}

但是,这并不能完全满足我的需要,因为参数是两份而不是一份。当我创建在堆上分配的新 std::function 时,我希望只发生一个副本。参数仍应通过引用数学成员函数的签名来传递。

【问题讨论】:

  • 你为什么需要invoke()?尤其是在使用fn 之前,您冒着myA 超出范围的风险?为什么不让调用者直接创建一个 lambda,然后根据需要传递它呢? A myA; B myB(5, 6, 7); auto fn = [=](){ myA.foo(myB); }; // use fn as needed...

标签: c++ class pointers templates


【解决方案1】:

您的 invoke 函数已被称为 std::bind,如果您想确保不复制 foo 的参数,则将 bind 与 cref:

#include <functional>

struct B {
  int a() const { return 1; }
};

struct A {
  void foo(const B& b) {
    int a = b.a();
  }
};

int main(void) {
  A myA;
  B myB;

  auto* fn = new std::function<void()>(std::bind(&A::foo, &myA, std::cref(myB)));
}

【讨论】:

    【解决方案2】:

    首先,在处理参数包时,您应该真正使用std::forward。现在您将所有参数作为值。

    其次,编译失败是因为void(T::* fn)(ARGS...)和ARGS... args之间的类型推导冲突。编译器会对是从 args 还是从您的函数中获取类型感到困惑。例如,A::foo 采用 const B&amp;,但您也给它一个值类型 B,这会导致冲突。所以你实际上需要两个单独的参数包来避免这种情况。

    #include <functional>
    #include <utility>
    
    // you didn't provide a definition of B, so this is what I had to come up with
    struct B {
        int x;
        int y;
        int z;
        int a() const {
            return x;
        }
    };
    
    class A{
    public:
        A() {}
    
        void foo(const B& b) {
           int a = b.a();
        }
    };
    
    // you don't actually need to create a function on the heap
    template<class T, typename ... FARGS, typename ...ARGS>
    std::function<void()> invoke(T *t, void(T::* fn)(FARGS...), ARGS &&... args) {    
         return [&, t]() { (t->*fn)( std::forward<ARGS>(args)... ); };
    }
    
    int main(void) {
        A myA;
        B myB{5, 6, 7};
    
        std::function<void()> fn = invoke(&myA, &A::foo, myB);
    }
    

    或者,您也可以使用std::bind,这正是您想要完成的工作:

    std::function<void()> fn = std::bind(&A::foo, &myA, myB);
    

    【讨论】:

    • 我在堆上创建函数,因为该指针实际上将被添加到 FreeRTOS 中的队列中,该队列使用 memcpy() 按值复制其数据。
    • 在这种情况下,您仍然可以像new std::function&lt;void()&gt;{std::move(fn)} 一样将此结果移动到堆上。这比在invoke 中强制堆分配更优雅。
    • 出于某种原因,如果我按照建议修改我的代码 ([&, t]() { (t->*fn)( std::forward(args)... ) ; };) 它会导致处理器在该点发生故障。但是,如果我将其保留为 lambda,使用按值复制并将 ARGS 作为 && 以进行完美转发,则一切正常,但是,由于某种原因,会生成 args 中值的两个副本而不是一个。
    • 我运行了这个确切的代码,它没有失败,所以我不确定它的原因是什么。仅从您的描述很难判断。根本不应该复制my,因为 lambda 存储的是引用而不是副本。
    • 我实际上想要在将函数添加到队列之前制作一个(并且只有一个)参数副本,因为无法保证接收线程将在何时何地访问数据。例如,如果我使用本地/临时(即在调用者的堆栈上)变量调用函数,则需要制作副本,以便数据可用于接收线程。
    【解决方案3】:

    invoke 的参数和您的成员函数的参数不相同。一个获得 const 引用,另一个没有。这需要反映在类型中。

    template<class T, 
             typename ... ARGS, 
             typename ... ARGS2> // <---- !!
    std::function<void()> * invoke(T *t, 
                                   void(T::* fn)(ARGS2...) // <---- !! 
                                   ARGS&&... args) { // you do want perfect forwarding
    

    【讨论】:

      【解决方案4】:

      我根据每个人的回答和 cmets 想出了一个适合我的解决方案。

      在我的应用程序中,我需要确保为正在调用的函数制作一个且只有一个参数副本,因为该函数将在不同的时间在不同的线程中执行,并且原始数据可能不是可用(例如堆栈上的临时变量)。按照建议,我已经修改了我的代码,以便在我的模板函数中使用完美转发。这有助于显着减少不必要的副本,但是,我仍然在我的调用函数中的 lambda 中获得了一个额外的副本。事实证明,我写错了我的数据类型的移动构造函数,所以当 lambda 创建数据的临时副本时,它必须被复制两次。

      这是我的工作代码的 sn-ps(使用我的示例数据类型 B):

      class B {
      public:
          B() : _a(1), _b(2), _c(3) {
      
          }
          B(int a, int b, int c) : _a(a), _b(b), _c(c) {
      
          }
          //Copy Constructor
          B(const B &v) : _a(v._a), _b(v._b), _c(v._c) {
              copyCount++;
          }
          //Move Constructor
          B(const B&& rhs): _a(rhs._a), _b(rhs._b), _c(rhs._c)
          {
      
          }
          B& operator=(const B &v) {
              this->_a = v._a;
              this->_b = v._b;
              this->_c = v._c;
      
              copyCount++;
      
              return *this;
          }
          ~B() {
      
          }
      
          inline int a() const { return _a; }
      
          static int copyCount;
      
      private:
          int _a;
          int _b;
          int _c;
      };
      
      template<class T, typename ... ARGSF, typename ... ARGS>
          inline static void invoke(T *t, void(T::*fn)(ARGSF...), ARGS&&... args) {
              std::function<void()> *f = new std::function<void()>([=]() { (t->*fn)( args... ); });
      
              //Queue in the parent thread
              if (!t->_parentThread->queueInvokable(f)) delete f;
          }
      
      template<class T>
          inline ConnectionHandle connect(T* t, void(T::* fn)(ARGS...)) {
              const auto lambda = [=](ARGS&&... args) { T::invoke(t, fn, args...); };
              return connect(lambda);
          }
      

      我在这里还展示了另一个函数 connect,它保存了一个 lambda 以供稍后调用,然后依次调用调用函数。

      有了这一切,只要创建一个数据类型的副本 a) 成员函数 (fn) 的参数是引用类型 b) 数据类型有一个有效的移动构造函数

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-12-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-25
        • 1970-01-01
        • 2010-09-13
        • 2020-09-20
        相关资源
        最近更新 更多